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_test_suite_root)
541 .arg("-L")
542 .arg(aux_dir)
543 .arg("-A")
544 .arg("internal_features");
545 self.set_revision_flags(&mut rustc);
546 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
547 rustc.args(&self.props.compile_flags);
548
549 self.compose_and_run_compiler(rustc, Some(src), self.testpaths)
550 }
551
552 fn maybe_add_external_args(&self, cmd: &mut Command, args: &Vec<String>) {
553 const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", "opt-level="];
558 const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", "debuginfo="];
559
560 let have_opt_flag =
564 self.props.compile_flags.iter().any(|arg| OPT_FLAGS.iter().any(|f| arg.starts_with(f)));
565 let have_debug_flag = self
566 .props
567 .compile_flags
568 .iter()
569 .any(|arg| DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)));
570
571 for arg in args {
572 if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag {
573 continue;
574 }
575 if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag {
576 continue;
577 }
578 cmd.arg(arg);
579 }
580 }
581
582 fn check_all_error_patterns(
583 &self,
584 output_to_check: &str,
585 proc_res: &ProcRes,
586 pm: Option<PassMode>,
587 ) {
588 if self.props.error_patterns.is_empty() && self.props.regex_error_patterns.is_empty() {
589 if pm.is_some() {
590 return;
592 } else {
593 self.fatal(&format!(
594 "no error pattern specified in {:?}",
595 self.testpaths.file.display()
596 ));
597 }
598 }
599
600 let mut missing_patterns: Vec<String> = Vec::new();
601
602 self.check_error_patterns(output_to_check, &mut missing_patterns);
603 self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns);
604
605 if missing_patterns.is_empty() {
606 return;
607 }
608
609 if missing_patterns.len() == 1 {
610 self.fatal_proc_rec(
611 &format!("error pattern '{}' not found!", missing_patterns[0]),
612 proc_res,
613 );
614 } else {
615 for pattern in missing_patterns {
616 self.error(&format!("error pattern '{}' not found!", pattern));
617 }
618 self.fatal_proc_rec("multiple error patterns not found", proc_res);
619 }
620 }
621
622 fn check_error_patterns(&self, output_to_check: &str, missing_patterns: &mut Vec<String>) {
623 debug!("check_error_patterns");
624 for pattern in &self.props.error_patterns {
625 if output_to_check.contains(pattern.trim()) {
626 debug!("found error pattern {}", pattern);
627 } else {
628 missing_patterns.push(pattern.to_string());
629 }
630 }
631 }
632
633 fn check_regex_error_patterns(
634 &self,
635 output_to_check: &str,
636 proc_res: &ProcRes,
637 missing_patterns: &mut Vec<String>,
638 ) {
639 debug!("check_regex_error_patterns");
640
641 for pattern in &self.props.regex_error_patterns {
642 let pattern = pattern.trim();
643 let re = match Regex::new(pattern) {
644 Ok(re) => re,
645 Err(err) => {
646 self.fatal_proc_rec(
647 &format!("invalid regex error pattern '{}': {:?}", pattern, err),
648 proc_res,
649 );
650 }
651 };
652 if re.is_match(output_to_check) {
653 debug!("found regex error pattern {}", pattern);
654 } else {
655 missing_patterns.push(pattern.to_string());
656 }
657 }
658 }
659
660 fn check_no_compiler_crash(&self, proc_res: &ProcRes, should_ice: bool) {
661 match proc_res.status.code() {
662 Some(101) if !should_ice => {
663 self.fatal_proc_rec("compiler encountered internal error", proc_res)
664 }
665 None => self.fatal_proc_rec("compiler terminated by signal", proc_res),
666 _ => (),
667 }
668 }
669
670 fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) {
671 for pat in &self.props.forbid_output {
672 if output_to_check.contains(pat) {
673 self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res);
674 }
675 }
676 }
677
678 fn check_expected_errors(&self, expected_errors: Vec<errors::Error>, proc_res: &ProcRes) {
679 debug!(
680 "check_expected_errors: expected_errors={:?} proc_res.status={:?}",
681 expected_errors, proc_res.status
682 );
683 if proc_res.status.success()
684 && expected_errors.iter().any(|x| x.kind == Some(ErrorKind::Error))
685 {
686 self.fatal_proc_rec("process did not return an error status", proc_res);
687 }
688
689 if self.props.known_bug {
690 if !expected_errors.is_empty() {
691 self.fatal_proc_rec(
692 "`known_bug` tests should not have an expected error",
693 proc_res,
694 );
695 }
696 return;
697 }
698
699 let file_name = format!("{}", self.testpaths.file.display()).replace(r"\", "/");
701
702 let diagnostic_file_name = if self.props.remap_src_base {
705 let mut p = PathBuf::from(FAKE_SRC_BASE);
706 p.push(&self.testpaths.relative_dir);
707 p.push(self.testpaths.file.file_name().unwrap());
708 p.display().to_string()
709 } else {
710 self.testpaths.file.display().to_string()
711 };
712
713 let expect_help = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Help));
718 let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note));
719
720 let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
722 let mut unexpected = Vec::new();
723 let mut found = vec![false; expected_errors.len()];
724 for mut actual_error in actual_errors {
725 actual_error.msg = self.normalize_output(&actual_error.msg, &[]);
726
727 let opt_index =
728 expected_errors.iter().enumerate().position(|(index, expected_error)| {
729 !found[index]
730 && actual_error.line_num == expected_error.line_num
731 && (expected_error.kind.is_none()
732 || actual_error.kind == expected_error.kind)
733 && actual_error.msg.contains(&expected_error.msg)
734 });
735
736 match opt_index {
737 Some(index) => {
738 assert!(!found[index]);
740 found[index] = true;
741 }
742
743 None => {
744 if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note)
746 {
747 self.error(&format!(
748 "{}:{}: unexpected {}: '{}'",
749 file_name,
750 actual_error.line_num_str(),
751 actual_error
752 .kind
753 .as_ref()
754 .map_or(String::from("message"), |k| k.to_string()),
755 actual_error.msg
756 ));
757 unexpected.push(actual_error);
758 }
759 }
760 }
761 }
762
763 let mut not_found = Vec::new();
764 for (index, expected_error) in expected_errors.iter().enumerate() {
766 if !found[index] {
767 self.error(&format!(
768 "{}:{}: expected {} not found: {}",
769 file_name,
770 expected_error.line_num_str(),
771 expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()),
772 expected_error.msg
773 ));
774 not_found.push(expected_error);
775 }
776 }
777
778 if !unexpected.is_empty() || !not_found.is_empty() {
779 self.error(&format!(
780 "{} unexpected errors found, {} expected errors not found",
781 unexpected.len(),
782 not_found.len()
783 ));
784 println!("status: {}\ncommand: {}\n", proc_res.status, proc_res.cmdline);
785 if !unexpected.is_empty() {
786 println!("{}", "--- unexpected errors (from JSON output) ---".green());
787 for error in &unexpected {
788 println!("{}", error.render_for_expected());
789 }
790 println!("{}", "---".green());
791 }
792 if !not_found.is_empty() {
793 println!("{}", "--- not found errors (from test file) ---".red());
794 for error in ¬_found {
795 println!("{}", error.render_for_expected());
796 }
797 println!("{}", "---\n".red());
798 }
799 panic!("errors differ from expected");
800 }
801 }
802
803 fn is_unexpected_compiler_message(
808 &self,
809 actual_error: &Error,
810 expect_help: bool,
811 expect_note: bool,
812 ) -> bool {
813 !actual_error.msg.is_empty()
814 && match actual_error.kind {
815 Some(ErrorKind::Help) => expect_help,
816 Some(ErrorKind::Note) => expect_note,
817 Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true,
818 Some(ErrorKind::Suggestion) | None => false,
819 }
820 }
821
822 fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {
823 match (pm, self.props.fail_mode, self.config.mode) {
824 (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), Ui) => Emit::Metadata,
825 _ => Emit::None,
826 }
827 }
828
829 fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes {
830 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), Vec::new())
831 }
832
833 fn compile_test_with_passes(
834 &self,
835 will_execute: WillExecute,
836 emit: Emit,
837 passes: Vec<String>,
838 ) -> ProcRes {
839 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), passes)
840 }
841
842 fn compile_test_general(
843 &self,
844 will_execute: WillExecute,
845 emit: Emit,
846 local_pm: Option<PassMode>,
847 passes: Vec<String>,
848 ) -> ProcRes {
849 let output_file = match will_execute {
851 WillExecute::Yes => TargetLocation::ThisFile(self.make_exe_name()),
852 WillExecute::No | WillExecute::Disabled => {
853 TargetLocation::ThisDirectory(self.output_base_dir())
854 }
855 };
856
857 let allow_unused = match self.config.mode {
858 Ui => {
859 if !self.is_rustdoc()
865 && local_pm != Some(PassMode::Run)
869 {
870 AllowUnused::Yes
871 } else {
872 AllowUnused::No
873 }
874 }
875 _ => AllowUnused::No,
876 };
877
878 let rustc = self.make_compile_args(
879 &self.testpaths.file,
880 output_file,
881 emit,
882 allow_unused,
883 LinkToAux::Yes,
884 passes,
885 );
886
887 self.compose_and_run_compiler(rustc, None, self.testpaths)
888 }
889
890 fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes {
893 if self.props.build_aux_docs {
894 for rel_ab in &self.props.aux.builds {
895 let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
896 let props_for_aux =
897 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
898 let aux_cx = TestCx {
899 config: self.config,
900 props: &props_for_aux,
901 testpaths: &aux_testpaths,
902 revision: self.revision,
903 };
904 create_dir_all(aux_cx.output_base_dir()).unwrap();
906 let auxres = aux_cx.document(&root_out_dir, root_testpaths);
909 if !auxres.status.success() {
910 return auxres;
911 }
912 }
913 }
914
915 let aux_dir = self.aux_output_dir_name();
916
917 let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed");
918
919 let out_dir: Cow<'_, Path> = if self.props.unique_doc_out_dir {
922 let file_name = self.testpaths.file.file_stem().expect("file name should not be empty");
923 let out_dir = PathBuf::from_iter([
924 root_out_dir,
925 Path::new("docs"),
926 Path::new(file_name),
927 Path::new("doc"),
928 ]);
929 create_dir_all(&out_dir).unwrap();
930 Cow::Owned(out_dir)
931 } else {
932 Cow::Borrowed(root_out_dir)
933 };
934
935 let mut rustdoc = Command::new(rustdoc_path);
936 let current_dir = output_base_dir(self.config, root_testpaths, self.safe_revision());
937 rustdoc.current_dir(current_dir);
938 rustdoc
939 .arg("-L")
940 .arg(self.config.run_lib_path.to_str().unwrap())
941 .arg("-L")
942 .arg(aux_dir)
943 .arg("-o")
944 .arg(out_dir.as_ref())
945 .arg("--deny")
946 .arg("warnings")
947 .arg(&self.testpaths.file)
948 .arg("-A")
949 .arg("internal_features")
950 .args(&self.props.compile_flags)
951 .args(&self.props.doc_flags);
952
953 if self.config.mode == RustdocJson {
954 rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
955 }
956
957 if let Some(ref linker) = self.config.target_linker {
958 rustdoc.arg(format!("-Clinker={}", linker));
959 }
960
961 self.compose_and_run_compiler(rustdoc, None, root_testpaths)
962 }
963
964 fn exec_compiled_test(&self) -> ProcRes {
965 self.exec_compiled_test_general(&[], true)
966 }
967
968 fn exec_compiled_test_general(
969 &self,
970 env_extra: &[(&str, &str)],
971 delete_after_success: bool,
972 ) -> ProcRes {
973 let prepare_env = |cmd: &mut Command| {
974 for key in &self.props.unset_exec_env {
975 cmd.env_remove(key);
976 }
977
978 for (key, val) in &self.props.exec_env {
979 cmd.env(key, val);
980 }
981 for (key, val) in env_extra {
982 cmd.env(key, val);
983 }
984 };
985
986 let proc_res = match &*self.config.target {
987 _ if self.config.remote_test_client.is_some() => {
1002 let aux_dir = self.aux_output_dir_name();
1003 let ProcArgs { prog, args } = self.make_run_args();
1004 let mut support_libs = Vec::new();
1005 if let Ok(entries) = aux_dir.read_dir() {
1006 for entry in entries {
1007 let entry = entry.unwrap();
1008 if !entry.path().is_file() {
1009 continue;
1010 }
1011 support_libs.push(entry.path());
1012 }
1013 }
1014 let mut test_client =
1015 Command::new(self.config.remote_test_client.as_ref().unwrap());
1016 test_client
1017 .args(&["run", &support_libs.len().to_string()])
1018 .arg(&prog)
1019 .args(support_libs)
1020 .args(args);
1021
1022 prepare_env(&mut test_client);
1023
1024 self.compose_and_run(
1025 test_client,
1026 self.config.run_lib_path.to_str().unwrap(),
1027 Some(aux_dir.to_str().unwrap()),
1028 None,
1029 )
1030 }
1031 _ if self.config.target.contains("vxworks") => {
1032 let aux_dir = self.aux_output_dir_name();
1033 let ProcArgs { prog, args } = self.make_run_args();
1034 let mut wr_run = Command::new("wr-run");
1035 wr_run.args(&[&prog]).args(args);
1036
1037 prepare_env(&mut wr_run);
1038
1039 self.compose_and_run(
1040 wr_run,
1041 self.config.run_lib_path.to_str().unwrap(),
1042 Some(aux_dir.to_str().unwrap()),
1043 None,
1044 )
1045 }
1046 _ => {
1047 let aux_dir = self.aux_output_dir_name();
1048 let ProcArgs { prog, args } = self.make_run_args();
1049 let mut program = Command::new(&prog);
1050 program.args(args).current_dir(&self.output_base_dir());
1051
1052 prepare_env(&mut program);
1053
1054 self.compose_and_run(
1055 program,
1056 self.config.run_lib_path.to_str().unwrap(),
1057 Some(aux_dir.to_str().unwrap()),
1058 None,
1059 )
1060 }
1061 };
1062
1063 if delete_after_success && proc_res.status.success() {
1064 let _ = fs::remove_file(self.make_exe_name());
1067 }
1068
1069 proc_res
1070 }
1071
1072 fn compute_aux_test_paths(&self, of: &TestPaths, rel_ab: &str) -> TestPaths {
1075 let test_ab =
1076 of.file.parent().expect("test file path has no parent").join("auxiliary").join(rel_ab);
1077 if !test_ab.exists() {
1078 self.fatal(&format!("aux-build `{}` source not found", test_ab.display()))
1079 }
1080
1081 TestPaths {
1082 file: test_ab,
1083 relative_dir: of
1084 .relative_dir
1085 .join(self.output_testname_unique())
1086 .join("auxiliary")
1087 .join(rel_ab)
1088 .parent()
1089 .expect("aux-build path has no parent")
1090 .to_path_buf(),
1091 }
1092 }
1093
1094 fn is_vxworks_pure_static(&self) -> bool {
1095 if self.config.target.contains("vxworks") {
1096 match env::var("RUST_VXWORKS_TEST_DYLINK") {
1097 Ok(s) => s != "1",
1098 _ => true,
1099 }
1100 } else {
1101 false
1102 }
1103 }
1104
1105 fn is_vxworks_pure_dynamic(&self) -> bool {
1106 self.config.target.contains("vxworks") && !self.is_vxworks_pure_static()
1107 }
1108
1109 fn has_aux_dir(&self) -> bool {
1110 !self.props.aux.builds.is_empty()
1111 || !self.props.aux.crates.is_empty()
1112 || !self.props.aux.proc_macros.is_empty()
1113 }
1114
1115 fn aux_output_dir(&self) -> PathBuf {
1116 let aux_dir = self.aux_output_dir_name();
1117
1118 if !self.props.aux.builds.is_empty() {
1119 remove_and_create_dir_all(&aux_dir);
1120 }
1121
1122 if !self.props.aux.bins.is_empty() {
1123 let aux_bin_dir = self.aux_bin_output_dir_name();
1124 remove_and_create_dir_all(&aux_dir);
1125 remove_and_create_dir_all(&aux_bin_dir);
1126 }
1127
1128 aux_dir
1129 }
1130
1131 fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Command) {
1132 for rel_ab in &self.props.aux.builds {
1133 self.build_auxiliary(of, rel_ab, &aux_dir, None);
1134 }
1135
1136 for rel_ab in &self.props.aux.bins {
1137 self.build_auxiliary(of, rel_ab, &aux_dir, Some(AuxType::Bin));
1138 }
1139
1140 let path_to_crate_name = |path: &str| -> String {
1141 path.rsplit_once('/')
1142 .map_or(path, |(_, tail)| tail)
1143 .trim_end_matches(".rs")
1144 .replace('-', "_")
1145 };
1146
1147 let add_extern =
1148 |rustc: &mut Command, aux_name: &str, aux_path: &str, aux_type: AuxType| {
1149 let lib_name = get_lib_name(&path_to_crate_name(aux_path), aux_type);
1150 if let Some(lib_name) = lib_name {
1151 rustc.arg("--extern").arg(format!(
1152 "{}={}/{}",
1153 aux_name,
1154 aux_dir.display(),
1155 lib_name
1156 ));
1157 }
1158 };
1159
1160 for (aux_name, aux_path) in &self.props.aux.crates {
1161 let aux_type = self.build_auxiliary(of, &aux_path, &aux_dir, None);
1162 add_extern(rustc, aux_name, aux_path, aux_type);
1163 }
1164
1165 for proc_macro in &self.props.aux.proc_macros {
1166 self.build_auxiliary(of, proc_macro, &aux_dir, Some(AuxType::ProcMacro));
1167 let crate_name = path_to_crate_name(proc_macro);
1168 add_extern(rustc, &crate_name, proc_macro, AuxType::ProcMacro);
1169 }
1170
1171 if let Some(aux_file) = &self.props.aux.codegen_backend {
1174 let aux_type = self.build_auxiliary(of, aux_file, aux_dir, None);
1175 if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
1176 let lib_path = aux_dir.join(&lib_name);
1177 rustc.arg(format!("-Zcodegen-backend={}", lib_path.display()));
1178 }
1179 }
1180 }
1181
1182 fn compose_and_run_compiler(
1185 &self,
1186 mut rustc: Command,
1187 input: Option<String>,
1188 root_testpaths: &TestPaths,
1189 ) -> ProcRes {
1190 if self.props.add_core_stubs {
1191 let minicore_path = self.build_minicore();
1192 rustc.arg("--extern");
1193 rustc.arg(&format!("minicore={}", minicore_path.to_str().unwrap()));
1194 }
1195
1196 let aux_dir = self.aux_output_dir();
1197 self.build_all_auxiliary(root_testpaths, &aux_dir, &mut rustc);
1198
1199 rustc.envs(self.props.rustc_env.clone());
1200 self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
1201 self.compose_and_run(
1202 rustc,
1203 self.config.compile_lib_path.to_str().unwrap(),
1204 Some(aux_dir.to_str().unwrap()),
1205 input,
1206 )
1207 }
1208
1209 fn build_minicore(&self) -> PathBuf {
1212 let output_file_path = self.output_base_dir().join("libminicore.rlib");
1213 let mut rustc = self.make_compile_args(
1214 &self.config.minicore_path,
1215 TargetLocation::ThisFile(output_file_path.clone()),
1216 Emit::None,
1217 AllowUnused::Yes,
1218 LinkToAux::No,
1219 vec![],
1220 );
1221
1222 rustc.args(&["--crate-type", "rlib"]);
1223 rustc.arg("-Cpanic=abort");
1224
1225 let res =
1226 self.compose_and_run(rustc, self.config.compile_lib_path.to_str().unwrap(), None, None);
1227 if !res.status.success() {
1228 self.fatal_proc_rec(
1229 &format!(
1230 "auxiliary build of {:?} failed to compile: ",
1231 self.config.minicore_path.display()
1232 ),
1233 &res,
1234 );
1235 }
1236
1237 output_file_path
1238 }
1239
1240 fn build_auxiliary(
1244 &self,
1245 of: &TestPaths,
1246 source_path: &str,
1247 aux_dir: &Path,
1248 aux_type: Option<AuxType>,
1249 ) -> AuxType {
1250 let aux_testpaths = self.compute_aux_test_paths(of, source_path);
1251 let mut aux_props =
1252 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
1253 if aux_type == Some(AuxType::ProcMacro) {
1254 aux_props.force_host = true;
1255 }
1256 let mut aux_dir = aux_dir.to_path_buf();
1257 if aux_type == Some(AuxType::Bin) {
1258 aux_dir.push("bin");
1262 }
1263 let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1264 let aux_cx = TestCx {
1265 config: self.config,
1266 props: &aux_props,
1267 testpaths: &aux_testpaths,
1268 revision: self.revision,
1269 };
1270 create_dir_all(aux_cx.output_base_dir()).unwrap();
1272 let input_file = &aux_testpaths.file;
1273 let mut aux_rustc = aux_cx.make_compile_args(
1274 input_file,
1275 aux_output,
1276 Emit::None,
1277 AllowUnused::No,
1278 LinkToAux::No,
1279 Vec::new(),
1280 );
1281 aux_cx.build_all_auxiliary(of, &aux_dir, &mut aux_rustc);
1282
1283 aux_rustc.envs(aux_props.rustc_env.clone());
1284 for key in &aux_props.unset_rustc_env {
1285 aux_rustc.env_remove(key);
1286 }
1287
1288 let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1289 (AuxType::Bin, Some("bin"))
1290 } else if aux_type == Some(AuxType::ProcMacro) {
1291 (AuxType::ProcMacro, Some("proc-macro"))
1292 } else if aux_type.is_some() {
1293 panic!("aux_type {aux_type:?} not expected");
1294 } else if aux_props.no_prefer_dynamic {
1295 (AuxType::Dylib, None)
1296 } else if self.config.target.contains("emscripten")
1297 || (self.config.target.contains("musl")
1298 && !aux_props.force_host
1299 && !self.config.host.contains("musl"))
1300 || self.config.target.contains("wasm32")
1301 || self.config.target.contains("nvptx")
1302 || self.is_vxworks_pure_static()
1303 || self.config.target.contains("bpf")
1304 || !self.config.target_cfg().dynamic_linking
1305 || matches!(self.config.mode, CoverageMap | CoverageRun)
1306 {
1307 (AuxType::Lib, Some("lib"))
1321 } else {
1322 (AuxType::Dylib, Some("dylib"))
1323 };
1324
1325 if let Some(crate_type) = crate_type {
1326 aux_rustc.args(&["--crate-type", crate_type]);
1327 }
1328
1329 if aux_type == AuxType::ProcMacro {
1330 aux_rustc.args(&["--extern", "proc_macro"]);
1332 }
1333
1334 aux_rustc.arg("-L").arg(&aux_dir);
1335
1336 let auxres = aux_cx.compose_and_run(
1337 aux_rustc,
1338 aux_cx.config.compile_lib_path.to_str().unwrap(),
1339 Some(aux_dir.to_str().unwrap()),
1340 None,
1341 );
1342 if !auxres.status.success() {
1343 self.fatal_proc_rec(
1344 &format!(
1345 "auxiliary build of {:?} failed to compile: ",
1346 aux_testpaths.file.display()
1347 ),
1348 &auxres,
1349 );
1350 }
1351 aux_type
1352 }
1353
1354 fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1355 let mut filter_paths_from_len = Vec::new();
1356 let mut add_path = |path: &Path| {
1357 let path = path.display().to_string();
1358 let windows = path.replace("\\", "\\\\");
1359 if windows != path {
1360 filter_paths_from_len.push(windows);
1361 }
1362 filter_paths_from_len.push(path);
1363 };
1364
1365 add_path(&self.config.src_test_suite_root);
1371 add_path(&self.config.build_test_suite_root);
1372
1373 read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1374 }
1375
1376 fn compose_and_run(
1377 &self,
1378 mut command: Command,
1379 lib_path: &str,
1380 aux_path: Option<&str>,
1381 input: Option<String>,
1382 ) -> ProcRes {
1383 let cmdline = {
1384 let cmdline = self.make_cmdline(&command, lib_path);
1385 logv(self.config, format!("executing {}", cmdline));
1386 cmdline
1387 };
1388
1389 command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1390
1391 add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1394
1395 let mut child = disable_error_reporting(|| command.spawn())
1396 .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1397 if let Some(input) = input {
1398 child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1399 }
1400
1401 let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1402
1403 let result = ProcRes {
1404 status,
1405 stdout: String::from_utf8_lossy(&stdout).into_owned(),
1406 stderr: String::from_utf8_lossy(&stderr).into_owned(),
1407 truncated,
1408 cmdline,
1409 };
1410
1411 self.dump_output(
1412 self.config.verbose,
1413 &command.get_program().to_string_lossy(),
1414 &result.stdout,
1415 &result.stderr,
1416 );
1417
1418 result
1419 }
1420
1421 fn is_rustdoc(&self) -> bool {
1422 matches!(self.config.suite.as_str(), "rustdoc-ui" | "rustdoc-js" | "rustdoc-json")
1423 }
1424
1425 fn get_mir_dump_dir(&self) -> PathBuf {
1426 let mut mir_dump_dir = self.config.build_test_suite_root.clone();
1427 debug!("input_file: {:?}", self.testpaths.file);
1428 mir_dump_dir.push(&self.testpaths.relative_dir);
1429 mir_dump_dir.push(self.testpaths.file.file_stem().unwrap());
1430 mir_dump_dir
1431 }
1432
1433 fn make_compile_args(
1434 &self,
1435 input_file: &Path,
1436 output_file: TargetLocation,
1437 emit: Emit,
1438 allow_unused: AllowUnused,
1439 link_to_aux: LinkToAux,
1440 passes: Vec<String>, ) -> Command {
1442 let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
1443 let is_rustdoc = self.is_rustdoc() && !is_aux;
1444 let mut rustc = if !is_rustdoc {
1445 Command::new(&self.config.rustc_path)
1446 } else {
1447 Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1448 };
1449 rustc.arg(input_file);
1450
1451 rustc.arg("-Zthreads=1");
1453
1454 rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1463 rustc.arg("-Ztranslate-remapped-path-to-local-path=no");
1464
1465 rustc.arg("-Z").arg(format!(
1470 "ignore-directory-in-diagnostics-source-blocks={}",
1471 home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1472 ));
1473 rustc.arg("-Z").arg(format!(
1475 "ignore-directory-in-diagnostics-source-blocks={}",
1476 self.config.src_root.join("vendor").to_str().unwrap(),
1477 ));
1478
1479 if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1481 && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1482 {
1483 rustc.arg("--sysroot").arg(&self.config.sysroot_base);
1485 }
1486
1487 let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1489
1490 if !custom_target {
1491 let target =
1492 if self.props.force_host { &*self.config.host } else { &*self.config.target };
1493
1494 rustc.arg(&format!("--target={}", target));
1495 }
1496 self.set_revision_flags(&mut rustc);
1497
1498 if !is_rustdoc {
1499 if let Some(ref incremental_dir) = self.props.incremental_dir {
1500 rustc.args(&["-C", &format!("incremental={}", incremental_dir.display())]);
1501 rustc.args(&["-Z", "incremental-verify-ich"]);
1502 }
1503
1504 if self.config.mode == CodegenUnits {
1505 rustc.args(&["-Z", "human_readable_cgu_names"]);
1506 }
1507 }
1508
1509 if self.config.optimize_tests && !is_rustdoc {
1510 match self.config.mode {
1511 Ui => {
1512 if self.config.optimize_tests
1517 && self.props.pass_mode(&self.config) == Some(PassMode::Run)
1518 && !self
1519 .props
1520 .compile_flags
1521 .iter()
1522 .any(|arg| arg == "-O" || arg.contains("opt-level"))
1523 {
1524 rustc.arg("-O");
1525 }
1526 }
1527 DebugInfo => { }
1528 CoverageMap | CoverageRun => {
1529 }
1534 _ => {
1535 rustc.arg("-O");
1536 }
1537 }
1538 }
1539
1540 let set_mir_dump_dir = |rustc: &mut Command| {
1541 let mir_dump_dir = self.get_mir_dump_dir();
1542 remove_and_create_dir_all(&mir_dump_dir);
1543 let mut dir_opt = "-Zdump-mir-dir=".to_string();
1544 dir_opt.push_str(mir_dump_dir.to_str().unwrap());
1545 debug!("dir_opt: {:?}", dir_opt);
1546 rustc.arg(dir_opt);
1547 };
1548
1549 match self.config.mode {
1550 Incremental => {
1551 if self.props.error_patterns.is_empty()
1555 && self.props.regex_error_patterns.is_empty()
1556 {
1557 rustc.args(&["--error-format", "json"]);
1558 rustc.args(&["--json", "future-incompat"]);
1559 }
1560 rustc.arg("-Zui-testing");
1561 rustc.arg("-Zdeduplicate-diagnostics=no");
1562 }
1563 Ui => {
1564 if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1565 rustc.args(&["--error-format", "json"]);
1566 rustc.args(&["--json", "future-incompat"]);
1567 }
1568 rustc.arg("-Ccodegen-units=1");
1569 rustc.arg("-Zui-testing");
1571 rustc.arg("-Zdeduplicate-diagnostics=no");
1572 rustc.arg("-Zwrite-long-types-to-disk=no");
1573 rustc.arg("-Cstrip=debuginfo");
1575 }
1576 MirOpt => {
1577 let zdump_arg = if !passes.is_empty() {
1581 format!("-Zdump-mir={}", passes.join(" | "))
1582 } else {
1583 "-Zdump-mir=all".to_string()
1584 };
1585
1586 rustc.args(&[
1587 "-Copt-level=1",
1588 &zdump_arg,
1589 "-Zvalidate-mir",
1590 "-Zlint-mir",
1591 "-Zdump-mir-exclude-pass-number",
1592 "-Zmir-include-spans=false", "--crate-type=rlib",
1594 ]);
1595 if let Some(pass) = &self.props.mir_unit_test {
1596 rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1597 } else {
1598 rustc.args(&[
1599 "-Zmir-opt-level=4",
1600 "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1601 ]);
1602 }
1603
1604 set_mir_dump_dir(&mut rustc);
1605 }
1606 CoverageMap => {
1607 rustc.arg("-Cinstrument-coverage");
1608 rustc.arg("-Zno-profiler-runtime");
1611 rustc.arg("-Copt-level=2");
1615 }
1616 CoverageRun => {
1617 rustc.arg("-Cinstrument-coverage");
1618 rustc.arg("-Copt-level=2");
1622 }
1623 Assembly | Codegen => {
1624 rustc.arg("-Cdebug-assertions=no");
1625 }
1626 Crashes => {
1627 set_mir_dump_dir(&mut rustc);
1628 }
1629 Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake | CodegenUnits | RustdocJs => {
1630 }
1632 }
1633
1634 if self.props.remap_src_base {
1635 rustc.arg(format!(
1636 "--remap-path-prefix={}={}",
1637 self.config.src_test_suite_root.to_str().unwrap(),
1638 FAKE_SRC_BASE,
1639 ));
1640 }
1641
1642 match emit {
1643 Emit::None => {}
1644 Emit::Metadata if is_rustdoc => {}
1645 Emit::Metadata => {
1646 rustc.args(&["--emit", "metadata"]);
1647 }
1648 Emit::LlvmIr => {
1649 rustc.args(&["--emit", "llvm-ir"]);
1650 }
1651 Emit::Mir => {
1652 rustc.args(&["--emit", "mir"]);
1653 }
1654 Emit::Asm => {
1655 rustc.args(&["--emit", "asm"]);
1656 }
1657 Emit::LinkArgsAsm => {
1658 rustc.args(&["-Clink-args=--emit=asm"]);
1659 }
1660 }
1661
1662 if !is_rustdoc {
1663 if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1664 } else if !self.props.no_prefer_dynamic {
1666 rustc.args(&["-C", "prefer-dynamic"]);
1667 }
1668 }
1669
1670 match output_file {
1671 _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1674 TargetLocation::ThisFile(path) => {
1675 rustc.arg("-o").arg(path);
1676 }
1677 TargetLocation::ThisDirectory(path) => {
1678 if is_rustdoc {
1679 rustc.arg("-o").arg(path);
1681 } else {
1682 rustc.arg("--out-dir").arg(path);
1683 }
1684 }
1685 }
1686
1687 match self.config.compare_mode {
1688 Some(CompareMode::Polonius) => {
1689 rustc.args(&["-Zpolonius"]);
1690 }
1691 Some(CompareMode::NextSolver) => {
1692 rustc.args(&["-Znext-solver"]);
1693 }
1694 Some(CompareMode::NextSolverCoherence) => {
1695 rustc.args(&["-Znext-solver=coherence"]);
1696 }
1697 Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1698 rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1699 }
1700 Some(CompareMode::SplitDwarf) => {
1701 rustc.args(&["-Csplit-debuginfo=unpacked"]);
1702 }
1703 Some(CompareMode::SplitDwarfSingle) => {
1704 rustc.args(&["-Csplit-debuginfo=packed"]);
1705 }
1706 None => {}
1707 }
1708
1709 if let AllowUnused::Yes = allow_unused {
1712 rustc.args(&["-A", "unused"]);
1713 }
1714
1715 rustc.args(&["-A", "internal_features"]);
1717
1718 if self.props.force_host {
1719 self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1720 if !is_rustdoc {
1721 if let Some(ref linker) = self.config.host_linker {
1722 rustc.arg(format!("-Clinker={}", linker));
1723 }
1724 }
1725 } else {
1726 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1727 if !is_rustdoc {
1728 if let Some(ref linker) = self.config.target_linker {
1729 rustc.arg(format!("-Clinker={}", linker));
1730 }
1731 }
1732 }
1733
1734 if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1736 rustc.arg("-Ctarget-feature=-crt-static");
1737 }
1738
1739 if let LinkToAux::Yes = link_to_aux {
1740 if self.has_aux_dir() {
1743 rustc.arg("-L").arg(self.aux_output_dir_name());
1744 }
1745 }
1746
1747 rustc.args(&self.props.compile_flags);
1748
1749 if self.props.add_core_stubs {
1755 rustc.arg("-Cpanic=abort");
1756 }
1757
1758 rustc
1759 }
1760
1761 fn make_exe_name(&self) -> PathBuf {
1762 let mut f = self.output_base_dir().join("a");
1767 if self.config.target.contains("emscripten") {
1769 f = f.with_extra_extension("js");
1770 } else if self.config.target.starts_with("wasm") {
1771 f = f.with_extra_extension("wasm");
1772 } else if self.config.target.contains("spirv") {
1773 f = f.with_extra_extension("spv");
1774 } else if !env::consts::EXE_SUFFIX.is_empty() {
1775 f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1776 }
1777 f
1778 }
1779
1780 fn make_run_args(&self) -> ProcArgs {
1781 let mut args = self.split_maybe_args(&self.config.runner);
1784
1785 let exe_file = self.make_exe_name();
1786
1787 args.push(exe_file.into_os_string());
1788
1789 args.extend(self.props.run_flags.iter().map(OsString::from));
1791
1792 let prog = args.remove(0);
1793 ProcArgs { prog, args }
1794 }
1795
1796 fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1797 match *argstr {
1798 Some(ref s) => s
1799 .split(' ')
1800 .filter_map(|s| {
1801 if s.chars().all(|c| c.is_whitespace()) {
1802 None
1803 } else {
1804 Some(OsString::from(s))
1805 }
1806 })
1807 .collect(),
1808 None => Vec::new(),
1809 }
1810 }
1811
1812 fn make_cmdline(&self, command: &Command, libpath: &str) -> String {
1813 use crate::util;
1814
1815 if cfg!(unix) {
1817 format!("{:?}", command)
1818 } else {
1819 fn lib_path_cmd_prefix(path: &str) -> String {
1822 format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1823 }
1824
1825 format!("{} {:?}", lib_path_cmd_prefix(libpath), command)
1826 }
1827 }
1828
1829 fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1830 let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1831
1832 self.dump_output_file(out, &format!("{}out", revision));
1833 self.dump_output_file(err, &format!("{}err", revision));
1834
1835 if !print_output {
1836 return;
1837 }
1838
1839 let path = Path::new(proc_name);
1840 let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1841 OsString::from_iter(
1842 path.parent()
1843 .unwrap()
1844 .file_name()
1845 .into_iter()
1846 .chain(Some(OsStr::new("/")))
1847 .chain(path.file_name()),
1848 )
1849 } else {
1850 path.file_name().unwrap().into()
1851 };
1852 let proc_name = proc_name.to_string_lossy();
1853 println!("------{proc_name} stdout------------------------------");
1854 println!("{}", out);
1855 println!("------{proc_name} stderr------------------------------");
1856 println!("{}", err);
1857 println!("------------------------------------------");
1858 }
1859
1860 fn dump_output_file(&self, out: &str, extension: &str) {
1861 let outfile = self.make_out_name(extension);
1862 fs::write(&outfile, out).unwrap();
1863 }
1864
1865 fn make_out_name(&self, extension: &str) -> PathBuf {
1868 self.output_base_name().with_extension(extension)
1869 }
1870
1871 fn aux_output_dir_name(&self) -> PathBuf {
1874 self.output_base_dir()
1875 .join("auxiliary")
1876 .with_extra_extension(self.config.mode.aux_dir_disambiguator())
1877 }
1878
1879 fn aux_bin_output_dir_name(&self) -> PathBuf {
1882 self.aux_output_dir_name().join("bin")
1883 }
1884
1885 fn output_testname_unique(&self) -> PathBuf {
1887 output_testname_unique(self.config, self.testpaths, self.safe_revision())
1888 }
1889
1890 fn safe_revision(&self) -> Option<&str> {
1893 if self.config.mode == Incremental { None } else { self.revision }
1894 }
1895
1896 fn output_base_dir(&self) -> PathBuf {
1900 output_base_dir(self.config, self.testpaths, self.safe_revision())
1901 }
1902
1903 fn output_base_name(&self) -> PathBuf {
1907 output_base_name(self.config, self.testpaths, self.safe_revision())
1908 }
1909
1910 fn error(&self, err: &str) {
1911 match self.revision {
1912 Some(rev) => println!("\nerror in revision `{}`: {}", rev, err),
1913 None => println!("\nerror: {}", err),
1914 }
1915 }
1916
1917 #[track_caller]
1918 fn fatal(&self, err: &str) -> ! {
1919 self.error(err);
1920 error!("fatal error, panic: {:?}", err);
1921 panic!("fatal error");
1922 }
1923
1924 fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
1925 self.error(err);
1926 proc_res.fatal(None, || ());
1927 }
1928
1929 fn fatal_proc_rec_with_ctx(
1930 &self,
1931 err: &str,
1932 proc_res: &ProcRes,
1933 on_failure: impl FnOnce(Self),
1934 ) -> ! {
1935 self.error(err);
1936 proc_res.fatal(None, || on_failure(*self));
1937 }
1938
1939 fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) {
1942 let output_path = self.output_base_name().with_extension("ll");
1943 let input_file = &self.testpaths.file;
1944 let rustc = self.make_compile_args(
1945 input_file,
1946 TargetLocation::ThisFile(output_path.clone()),
1947 Emit::LlvmIr,
1948 AllowUnused::No,
1949 LinkToAux::Yes,
1950 Vec::new(),
1951 );
1952
1953 let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
1954 (proc_res, output_path)
1955 }
1956
1957 fn verify_with_filecheck(&self, output: &Path) -> ProcRes {
1958 let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
1959 filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
1960
1961 filecheck.arg("--check-prefix=CHECK");
1963
1964 if let Some(rev) = self.revision {
1972 filecheck.arg("--check-prefix").arg(rev);
1973 }
1974
1975 filecheck.arg("--allow-unused-prefixes");
1979
1980 filecheck.args(&["--dump-input-context", "100"]);
1982
1983 filecheck.args(&self.props.filecheck_flags);
1985
1986 self.compose_and_run(filecheck, "", None, None)
1987 }
1988
1989 fn charset() -> &'static str {
1990 if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
1992 }
1993
1994 fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
1995 if !self.config.has_html_tidy {
1996 return;
1997 }
1998 println!("info: generating a diff against nightly rustdoc");
1999
2000 let suffix =
2001 self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2002 let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2003 remove_and_create_dir_all(&compare_dir);
2004
2005 let new_rustdoc = TestCx {
2007 config: &Config {
2008 rustdoc_path: Some("rustdoc".into()),
2011 rustc_path: "rustc".into(),
2013 ..self.config.clone()
2014 },
2015 ..*self
2016 };
2017
2018 let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2019 let mut rustc = new_rustdoc.make_compile_args(
2020 &new_rustdoc.testpaths.file,
2021 output_file,
2022 Emit::None,
2023 AllowUnused::Yes,
2024 LinkToAux::Yes,
2025 Vec::new(),
2026 );
2027 let aux_dir = new_rustdoc.aux_output_dir();
2028 new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);
2029
2030 let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
2031 if !proc_res.status.success() {
2032 eprintln!("failed to run nightly rustdoc");
2033 return;
2034 }
2035
2036 #[rustfmt::skip]
2037 let tidy_args = [
2038 "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar",
2039 "--indent", "yes",
2040 "--indent-spaces", "2",
2041 "--wrap", "0",
2042 "--show-warnings", "no",
2043 "--markup", "yes",
2044 "--quiet", "yes",
2045 "-modify",
2046 ];
2047 let tidy_dir = |dir| {
2048 for entry in walkdir::WalkDir::new(dir) {
2049 let entry = entry.expect("failed to read file");
2050 if entry.file_type().is_file()
2051 && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2052 {
2053 let status =
2054 Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2055 assert!(status.success() || status.code() == Some(1));
2057 }
2058 }
2059 };
2060 tidy_dir(out_dir);
2061 tidy_dir(&compare_dir);
2062
2063 let pager = {
2064 let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2065 output.and_then(|out| {
2066 if out.status.success() {
2067 Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2068 } else {
2069 None
2070 }
2071 })
2072 };
2073
2074 let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2075
2076 if !write_filtered_diff(
2077 &diff_filename,
2078 out_dir,
2079 &compare_dir,
2080 self.config.verbose,
2081 |file_type, extension| {
2082 file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2083 },
2084 ) {
2085 return;
2086 }
2087
2088 match self.config.color {
2089 ColorConfig::AlwaysColor => colored::control::set_override(true),
2090 ColorConfig::NeverColor => colored::control::set_override(false),
2091 _ => {}
2092 }
2093
2094 if let Some(pager) = pager {
2095 let pager = pager.trim();
2096 if self.config.verbose {
2097 eprintln!("using pager {}", pager);
2098 }
2099 let output = Command::new(pager)
2100 .env("PAGER", "")
2102 .stdin(File::open(&diff_filename).unwrap())
2103 .output()
2106 .unwrap();
2107 assert!(output.status.success());
2108 println!("{}", String::from_utf8_lossy(&output.stdout));
2109 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
2110 } else {
2111 use colored::Colorize;
2112 eprintln!("warning: no pager configured, falling back to unified diff");
2113 eprintln!(
2114 "help: try configuring a git pager (e.g. `delta`) with `git config --global core.pager delta`"
2115 );
2116 let mut out = io::stdout();
2117 let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2118 let mut line = Vec::new();
2119 loop {
2120 line.truncate(0);
2121 match diff.read_until(b'\n', &mut line) {
2122 Ok(0) => break,
2123 Ok(_) => {}
2124 Err(e) => eprintln!("ERROR: {:?}", e),
2125 }
2126 match String::from_utf8(line.clone()) {
2127 Ok(line) => {
2128 if line.starts_with('+') {
2129 write!(&mut out, "{}", line.green()).unwrap();
2130 } else if line.starts_with('-') {
2131 write!(&mut out, "{}", line.red()).unwrap();
2132 } else if line.starts_with('@') {
2133 write!(&mut out, "{}", line.blue()).unwrap();
2134 } else {
2135 out.write_all(line.as_bytes()).unwrap();
2136 }
2137 }
2138 Err(_) => {
2139 write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2140 }
2141 }
2142 }
2143 };
2144 }
2145
2146 fn get_lines<P: AsRef<Path>>(
2147 &self,
2148 path: &P,
2149 mut other_files: Option<&mut Vec<String>>,
2150 ) -> Vec<usize> {
2151 let content = fs::read_to_string(&path).unwrap();
2152 let mut ignore = false;
2153 content
2154 .lines()
2155 .enumerate()
2156 .filter_map(|(line_nb, line)| {
2157 if (line.trim_start().starts_with("pub mod ")
2158 || line.trim_start().starts_with("mod "))
2159 && line.ends_with(';')
2160 {
2161 if let Some(ref mut other_files) = other_files {
2162 other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2163 }
2164 None
2165 } else {
2166 let sline = line.rsplit("///").next().unwrap();
2167 let line = sline.trim_start();
2168 if line.starts_with("```") {
2169 if ignore {
2170 ignore = false;
2171 None
2172 } else {
2173 ignore = true;
2174 Some(line_nb + 1)
2175 }
2176 } else {
2177 None
2178 }
2179 }
2180 })
2181 .collect()
2182 }
2183
2184 fn check_rustdoc_test_option(&self, res: ProcRes) {
2189 let mut other_files = Vec::new();
2190 let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2191 let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2192 let normalized = normalized.to_str().unwrap().replace('\\', "/");
2193 files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2194 for other_file in other_files {
2195 let mut path = self.testpaths.file.clone();
2196 path.set_file_name(&format!("{}.rs", other_file));
2197 let path = fs::canonicalize(path).expect("failed to canonicalize");
2198 let normalized = path.to_str().unwrap().replace('\\', "/");
2199 files.insert(normalized, self.get_lines(&path, None));
2200 }
2201
2202 let mut tested = 0;
2203 for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2204 if let Some((left, right)) = s.split_once(" - ") {
2205 let path = left.rsplit("test ").next().unwrap();
2206 let path = fs::canonicalize(&path).expect("failed to canonicalize");
2207 let path = path.to_str().unwrap().replace('\\', "/");
2208 if let Some(ref mut v) = files.get_mut(&path) {
2209 tested += 1;
2210 let mut iter = right.split("(line ");
2211 iter.next();
2212 let line = iter
2213 .next()
2214 .unwrap_or(")")
2215 .split(')')
2216 .next()
2217 .unwrap_or("0")
2218 .parse()
2219 .unwrap_or(0);
2220 if let Ok(pos) = v.binary_search(&line) {
2221 v.remove(pos);
2222 } else {
2223 self.fatal_proc_rec(
2224 &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2225 &res,
2226 );
2227 }
2228 }
2229 }
2230 }) {}
2231 if tested == 0 {
2232 self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2233 } else {
2234 for (entry, v) in &files {
2235 if !v.is_empty() {
2236 self.fatal_proc_rec(
2237 &format!(
2238 "Not found test at line{} \"{}\":{:?}",
2239 if v.len() > 1 { "s" } else { "" },
2240 entry,
2241 v
2242 ),
2243 &res,
2244 );
2245 }
2246 }
2247 }
2248 }
2249
2250 fn force_color_svg(&self) -> bool {
2251 self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2252 }
2253
2254 fn load_compare_outputs(
2255 &self,
2256 proc_res: &ProcRes,
2257 output_kind: TestOutput,
2258 explicit_format: bool,
2259 ) -> usize {
2260 let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2261 let (stderr_kind, stdout_kind) = match output_kind {
2262 TestOutput::Compile => (
2263 if self.force_color_svg() {
2264 if self.config.target.contains("windows") {
2265 UI_WINDOWS_SVG
2268 } else {
2269 UI_SVG
2270 }
2271 } else if self.props.stderr_per_bitwidth {
2272 &stderr_bits
2273 } else {
2274 UI_STDERR
2275 },
2276 UI_STDOUT,
2277 ),
2278 TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2279 };
2280
2281 let expected_stderr = self.load_expected_output(stderr_kind);
2282 let expected_stdout = self.load_expected_output(stdout_kind);
2283
2284 let mut normalized_stdout =
2285 self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2286 match output_kind {
2287 TestOutput::Run if self.config.remote_test_client.is_some() => {
2288 normalized_stdout = static_regex!(
2293 "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2294 )
2295 .replace(&normalized_stdout, "")
2296 .to_string();
2297 normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2300 .replace(&normalized_stdout, "")
2301 .to_string();
2302 }
2305 _ => {}
2306 };
2307
2308 let stderr = if self.force_color_svg() {
2309 anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2310 } else if explicit_format {
2311 proc_res.stderr.clone()
2312 } else {
2313 json::extract_rendered(&proc_res.stderr)
2314 };
2315
2316 let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2317 let mut errors = 0;
2318 match output_kind {
2319 TestOutput::Compile => {
2320 if !self.props.dont_check_compiler_stdout {
2321 if self
2322 .compare_output(
2323 stdout_kind,
2324 &normalized_stdout,
2325 &proc_res.stdout,
2326 &expected_stdout,
2327 )
2328 .should_error()
2329 {
2330 errors += 1;
2331 }
2332 }
2333 if !self.props.dont_check_compiler_stderr {
2334 if self
2335 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2336 .should_error()
2337 {
2338 errors += 1;
2339 }
2340 }
2341 }
2342 TestOutput::Run => {
2343 if self
2344 .compare_output(
2345 stdout_kind,
2346 &normalized_stdout,
2347 &proc_res.stdout,
2348 &expected_stdout,
2349 )
2350 .should_error()
2351 {
2352 errors += 1;
2353 }
2354
2355 if self
2356 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2357 .should_error()
2358 {
2359 errors += 1;
2360 }
2361 }
2362 }
2363 errors
2364 }
2365
2366 fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2367 let rflags = self.props.run_flags.join(" ");
2370 let cflags = self.props.compile_flags.join(" ");
2371 let json = rflags.contains("--format json")
2372 || rflags.contains("--format=json")
2373 || cflags.contains("--error-format json")
2374 || cflags.contains("--error-format pretty-json")
2375 || cflags.contains("--error-format=json")
2376 || cflags.contains("--error-format=pretty-json")
2377 || cflags.contains("--output-format json")
2378 || cflags.contains("--output-format=json");
2379
2380 let mut normalized = output.to_string();
2381
2382 let mut normalize_path = |from: &Path, to: &str| {
2383 let mut from = from.display().to_string();
2384 if json {
2385 from = from.replace("\\", "\\\\");
2386 }
2387 normalized = normalized.replace(&from, to);
2388 };
2389
2390 let parent_dir = self.testpaths.file.parent().unwrap();
2391 normalize_path(parent_dir, "$DIR");
2392
2393 if self.props.remap_src_base {
2394 let mut remapped_parent_dir = PathBuf::from(FAKE_SRC_BASE);
2395 if self.testpaths.relative_dir != Path::new("") {
2396 remapped_parent_dir.push(&self.testpaths.relative_dir);
2397 }
2398 normalize_path(&remapped_parent_dir, "$DIR");
2399 }
2400
2401 let base_dir = Path::new("/rustc/FAKE_PREFIX");
2402 normalize_path(&base_dir.join("library"), "$SRC_DIR");
2404 normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2408
2409 let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2411 rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir.display()));
2412 let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
2413 normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2414
2415 normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2418 normalize_path(&self.config.build_root, "$BUILD_DIR");
2420
2421 if json {
2422 normalized = normalized.replace("\\n", "\n");
2427 }
2428
2429 normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2434 .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2435 .into_owned();
2436
2437 normalized = Self::normalize_platform_differences(&normalized);
2438
2439 normalized =
2441 static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2442 .replace_all(&normalized, |caps: &Captures<'_>| {
2443 format!(
2444 "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2445 filename = &caps["filename"]
2446 )
2447 })
2448 .into_owned();
2449
2450 normalized = normalized.replace("\t", "\\t"); normalized =
2457 static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2458
2459 let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2462 let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2463
2464 const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2465 if v0_crate_hash_prefix_re.is_match(&normalized) {
2466 normalized =
2468 v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2469 }
2470
2471 let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2472 let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2473
2474 const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2475 if v0_back_ref_prefix_re.is_match(&normalized) {
2476 normalized =
2478 v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2479 }
2480
2481 {
2488 let mut seen_allocs = indexmap::IndexSet::new();
2489
2490 normalized = static_regex!(
2492 r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2493 )
2494 .replace_all(&normalized, |caps: &Captures<'_>| {
2495 let index = caps.get(2).unwrap().as_str().to_string();
2497 let (index, _) = seen_allocs.insert_full(index);
2498 let offset = caps.get(3).map_or("", |c| c.as_str());
2499 let imm = caps.get(4).map_or("", |c| c.as_str());
2500 format!("╾ALLOC{index}{offset}{imm}╼")
2502 })
2503 .into_owned();
2504
2505 normalized = static_regex!(r"\balloc([0-9]+)\b")
2507 .replace_all(&normalized, |caps: &Captures<'_>| {
2508 let index = caps.get(1).unwrap().as_str().to_string();
2509 let (index, _) = seen_allocs.insert_full(index);
2510 format!("ALLOC{index}")
2511 })
2512 .into_owned();
2513 }
2514
2515 for rule in custom_rules {
2517 let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2518 normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2519 }
2520 normalized
2521 }
2522
2523 fn normalize_platform_differences(output: &str) -> String {
2529 let output = output.replace(r"\\", r"\");
2530
2531 static_regex!(
2536 r#"(?x)
2537 (?:
2538 # Match paths that don't include spaces.
2539 (?:\\[\pL\pN\.\-_']+)+\.\pL+
2540 |
2541 # If the path starts with a well-known root, then allow spaces and no file extension.
2542 \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2543 )"#
2544 )
2545 .replace_all(&output, |caps: &Captures<'_>| {
2546 println!("{}", &caps[0]);
2547 caps[0].replace(r"\", "/")
2548 })
2549 .replace("\r\n", "\n")
2550 }
2551
2552 fn expected_output_path(&self, kind: &str) -> PathBuf {
2553 let mut path =
2554 expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2555
2556 if !path.exists() {
2557 if let Some(CompareMode::Polonius) = self.config.compare_mode {
2558 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2559 }
2560 }
2561
2562 if !path.exists() {
2563 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2564 }
2565
2566 path
2567 }
2568
2569 fn load_expected_output(&self, kind: &str) -> String {
2570 let path = self.expected_output_path(kind);
2571 if path.exists() {
2572 match self.load_expected_output_from_path(&path) {
2573 Ok(x) => x,
2574 Err(x) => self.fatal(&x),
2575 }
2576 } else {
2577 String::new()
2578 }
2579 }
2580
2581 fn load_expected_output_from_path(&self, path: &Path) -> Result<String, String> {
2582 fs::read_to_string(path).map_err(|err| {
2583 format!("failed to load expected output from `{}`: {}", path.display(), err)
2584 })
2585 }
2586
2587 fn delete_file(&self, file: &Path) {
2588 if !file.exists() {
2589 return;
2591 }
2592 if let Err(e) = fs::remove_file(file) {
2593 self.fatal(&format!("failed to delete `{}`: {}", file.display(), e,));
2594 }
2595 }
2596
2597 fn compare_output(
2598 &self,
2599 stream: &str,
2600 actual: &str,
2601 actual_unnormalized: &str,
2602 expected: &str,
2603 ) -> CompareOutcome {
2604 let expected_path =
2605 expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
2606
2607 if self.config.bless && actual.is_empty() && expected_path.exists() {
2608 self.delete_file(&expected_path);
2609 }
2610
2611 let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2612 (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2615 _ => expected != actual,
2616 };
2617 if !are_different {
2618 return CompareOutcome::Same;
2619 }
2620
2621 let compare_output_by_lines = self.config.runner.is_some();
2625
2626 let tmp;
2627 let (expected, actual): (&str, &str) = if compare_output_by_lines {
2628 let actual_lines: HashSet<_> = actual.lines().collect();
2629 let expected_lines: Vec<_> = expected.lines().collect();
2630 let mut used = expected_lines.clone();
2631 used.retain(|line| actual_lines.contains(line));
2632 if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2634 return CompareOutcome::Same;
2635 }
2636 if expected_lines.is_empty() {
2637 ("", actual)
2639 } else {
2640 tmp = (expected_lines.join("\n"), used.join("\n"));
2641 (&tmp.0, &tmp.1)
2642 }
2643 } else {
2644 (expected, actual)
2645 };
2646
2647 let test_name = self.config.compare_mode.as_ref().map_or("", |m| m.to_str());
2649 let actual_path = self
2650 .output_base_name()
2651 .with_extra_extension(self.revision.unwrap_or(""))
2652 .with_extra_extension(test_name)
2653 .with_extra_extension(stream);
2654
2655 if let Err(err) = fs::write(&actual_path, &actual) {
2656 self.fatal(&format!("failed to write {stream} to `{actual_path:?}`: {err}",));
2657 }
2658 println!("Saved the actual {stream} to {actual_path:?}");
2659
2660 if !self.config.bless {
2661 if expected.is_empty() {
2662 println!("normalized {}:\n{}\n", stream, actual);
2663 } else {
2664 self.show_diff(
2665 stream,
2666 &expected_path,
2667 &actual_path,
2668 expected,
2669 actual,
2670 actual_unnormalized,
2671 );
2672 }
2673 } else {
2674 if self.revision.is_some() {
2677 let old =
2678 expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2679 self.delete_file(&old);
2680 }
2681
2682 if !actual.is_empty() {
2683 if let Err(err) = fs::write(&expected_path, &actual) {
2684 self.fatal(&format!("failed to write {stream} to `{expected_path:?}`: {err}"));
2685 }
2686 println!("Blessing the {stream} of {test_name} in {expected_path:?}");
2687 }
2688 }
2689
2690 println!("\nThe actual {0} differed from the expected {0}.", stream);
2691
2692 if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2693 }
2694
2695 fn show_diff(
2697 &self,
2698 stream: &str,
2699 expected_path: &Path,
2700 actual_path: &Path,
2701 expected: &str,
2702 actual: &str,
2703 actual_unnormalized: &str,
2704 ) {
2705 eprintln!("diff of {stream}:\n");
2706 if let Some(diff_command) = self.config.diff_command.as_deref() {
2707 let mut args = diff_command.split_whitespace();
2708 let name = args.next().unwrap();
2709 match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2710 Err(err) => {
2711 self.fatal(&format!(
2712 "failed to call custom diff command `{diff_command}`: {err}"
2713 ));
2714 }
2715 Ok(output) => {
2716 let output = String::from_utf8_lossy(&output.stdout);
2717 eprint!("{output}");
2718 }
2719 }
2720 } else {
2721 eprint!("{}", write_diff(expected, actual, 3));
2722 }
2723
2724 let diff_results = make_diff(actual, expected, 0);
2726
2727 let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2728 for hunk in diff_results {
2729 let mut line_no = hunk.line_number;
2730 for line in hunk.lines {
2731 if let DiffLine::Expected(normalized) = line {
2733 mismatches_normalized += &normalized;
2734 mismatches_normalized += "\n";
2735 mismatch_line_nos.push(line_no);
2736 line_no += 1;
2737 }
2738 }
2739 }
2740 let mut mismatches_unnormalized = String::new();
2741 let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2742 for hunk in diff_normalized {
2743 if mismatch_line_nos.contains(&hunk.line_number) {
2744 for line in hunk.lines {
2745 if let DiffLine::Resulting(unnormalized) = line {
2746 mismatches_unnormalized += &unnormalized;
2747 mismatches_unnormalized += "\n";
2748 }
2749 }
2750 }
2751 }
2752
2753 let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2754 if !normalized_diff.is_empty()
2756 && !mismatches_unnormalized.is_empty()
2757 && !mismatches_normalized.is_empty()
2758 {
2759 eprintln!("Note: some mismatched output was normalized before being compared");
2760 eprint!("{}", write_diff(&mismatches_unnormalized, &mismatches_normalized, 0));
2762 }
2763 }
2764
2765 fn check_and_prune_duplicate_outputs(
2766 &self,
2767 proc_res: &ProcRes,
2768 modes: &[CompareMode],
2769 require_same_modes: &[CompareMode],
2770 ) {
2771 for kind in UI_EXTENSIONS {
2772 let canon_comparison_path =
2773 expected_output_path(&self.testpaths, self.revision, &None, kind);
2774
2775 let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2776 Ok(canon) => canon,
2777 _ => continue,
2778 };
2779 let bless = self.config.bless;
2780 let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2781 let examined_path =
2782 expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind);
2783
2784 let examined_content = match self.load_expected_output_from_path(&examined_path) {
2786 Ok(content) => content,
2787 _ => return,
2788 };
2789
2790 let is_duplicate = canon == examined_content;
2791
2792 match (bless, require_same, is_duplicate) {
2793 (true, _, true) => {
2795 self.delete_file(&examined_path);
2796 }
2797 (_, true, false) => {
2800 self.fatal_proc_rec(
2801 &format!("`{}` should not have different output from base test!", kind),
2802 proc_res,
2803 );
2804 }
2805 _ => {}
2806 }
2807 };
2808 for mode in modes {
2809 check_and_prune_duplicate_outputs(mode, false);
2810 }
2811 for mode in require_same_modes {
2812 check_and_prune_duplicate_outputs(mode, true);
2813 }
2814 }
2815 }
2816
2817 fn create_stamp(&self) {
2818 let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision);
2819 fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap();
2820 }
2821
2822 fn init_incremental_test(&self) {
2823 let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
2830 if incremental_dir.exists() {
2831 let canonicalized = incremental_dir.canonicalize().unwrap();
2834 fs::remove_dir_all(canonicalized).unwrap();
2835 }
2836 fs::create_dir_all(&incremental_dir).unwrap();
2837
2838 if self.config.verbose {
2839 println!("init_incremental_test: incremental_dir={}", incremental_dir.display());
2840 }
2841 }
2842}
2843
2844struct ProcArgs {
2845 prog: OsString,
2846 args: Vec<OsString>,
2847}
2848
2849pub struct ProcRes {
2850 status: ExitStatus,
2851 stdout: String,
2852 stderr: String,
2853 truncated: Truncated,
2854 cmdline: String,
2855}
2856
2857impl ProcRes {
2858 pub fn print_info(&self) {
2859 fn render(name: &str, contents: &str) -> String {
2860 let contents = json::extract_rendered(contents);
2861 let contents = contents.trim_end();
2862 if contents.is_empty() {
2863 format!("{name}: none")
2864 } else {
2865 format!(
2866 "\
2867 --- {name} -------------------------------\n\
2868 {contents}\n\
2869 ------------------------------------------",
2870 )
2871 }
2872 }
2873
2874 println!(
2875 "status: {}\ncommand: {}\n{}\n{}\n",
2876 self.status,
2877 self.cmdline,
2878 render("stdout", &self.stdout),
2879 render("stderr", &self.stderr),
2880 );
2881 }
2882
2883 pub fn fatal(&self, err: Option<&str>, on_failure: impl FnOnce()) -> ! {
2884 if let Some(e) = err {
2885 println!("\nerror: {}", e);
2886 }
2887 self.print_info();
2888 on_failure();
2889 std::panic::resume_unwind(Box::new(()));
2892 }
2893}
2894
2895#[derive(Debug)]
2896enum TargetLocation {
2897 ThisFile(PathBuf),
2898 ThisDirectory(PathBuf),
2899}
2900
2901enum AllowUnused {
2902 Yes,
2903 No,
2904}
2905
2906enum LinkToAux {
2907 Yes,
2908 No,
2909}
2910
2911#[derive(Debug, PartialEq)]
2912enum AuxType {
2913 Bin,
2914 Lib,
2915 Dylib,
2916 ProcMacro,
2917}
2918
2919#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2922enum CompareOutcome {
2923 Same,
2925 Blessed,
2927 Differed,
2929}
2930
2931impl CompareOutcome {
2932 fn should_error(&self) -> bool {
2933 matches!(self, CompareOutcome::Differed)
2934 }
2935}