1use std::borrow::Cow;
2use std::collections::{HashMap, HashSet};
3use std::ffi::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::process::{Child, Command, ExitStatus, Output, Stdio};
9use std::sync::Arc;
10use std::{env, iter, str};
11
12use build_helper::fs::remove_and_create_dir_all;
13use camino::{Utf8Path, Utf8PathBuf};
14use colored::{Color, 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::directives::TestProps;
27use crate::errors::{Error, ErrorKind, load_errors};
28use crate::read2::{Truncated, read2_abbreviated};
29use crate::util::{Utf8PathBufExt, add_dylib_path, logv, static_regex};
30use crate::{ColorConfig, help, json, stamp_file_path, warning};
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() {
128 panic!("gdb not available but debuginfo gdb debuginfo test requested");
129 }
130 }
131 }
132
133 if config.verbose {
134 print!("\n\n");
136 }
137 debug!("running {}", testpaths.file);
138 let mut props = TestProps::from_file(&testpaths.file, revision, &config);
139
140 if props.incremental {
144 props.incremental_dir = Some(incremental_dir(&config, testpaths, revision));
145 }
146
147 let cx = TestCx { config: &config, props: &props, testpaths, revision };
148
149 if let Err(e) = create_dir_all(&cx.output_base_dir()) {
150 panic!("failed to create output base directory {}: {e}", cx.output_base_dir());
151 }
152
153 if props.incremental {
154 cx.init_incremental_test();
155 }
156
157 if config.mode == Incremental {
158 assert!(!props.revisions.is_empty(), "Incremental tests require revisions.");
161 for revision in &props.revisions {
162 let mut revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config);
163 revision_props.incremental_dir = props.incremental_dir.clone();
164 let rev_cx = TestCx {
165 config: &config,
166 props: &revision_props,
167 testpaths,
168 revision: Some(revision),
169 };
170 rev_cx.run_revision();
171 }
172 } else {
173 cx.run_revision();
174 }
175
176 cx.create_stamp();
177}
178
179pub fn compute_stamp_hash(config: &Config) -> String {
180 let mut hash = DefaultHasher::new();
181 config.stage_id.hash(&mut hash);
182 config.run.hash(&mut hash);
183 config.edition.hash(&mut hash);
184
185 match config.debugger {
186 Some(Debugger::Cdb) => {
187 config.cdb.hash(&mut hash);
188 }
189
190 Some(Debugger::Gdb) => {
191 config.gdb.hash(&mut hash);
192 env::var_os("PATH").hash(&mut hash);
193 env::var_os("PYTHONPATH").hash(&mut hash);
194 }
195
196 Some(Debugger::Lldb) => {
197 config.python.hash(&mut hash);
198 config.lldb_python_dir.hash(&mut hash);
199 env::var_os("PATH").hash(&mut hash);
200 env::var_os("PYTHONPATH").hash(&mut hash);
201 }
202
203 None => {}
204 }
205
206 if let Ui = config.mode {
207 config.force_pass_mode.hash(&mut hash);
208 }
209
210 format!("{:x}", hash.finish())
211}
212
213#[derive(Copy, Clone, Debug)]
214struct TestCx<'test> {
215 config: &'test Config,
216 props: &'test TestProps,
217 testpaths: &'test TestPaths,
218 revision: Option<&'test str>,
219}
220
221enum ReadFrom {
222 Path,
223 Stdin(String),
224}
225
226enum TestOutput {
227 Compile,
228 Run,
229}
230
231#[derive(Copy, Clone, PartialEq)]
233enum WillExecute {
234 Yes,
235 No,
236 Disabled,
237}
238
239#[derive(Copy, Clone)]
241enum Emit {
242 None,
243 Metadata,
244 LlvmIr,
245 Mir,
246 Asm,
247 LinkArgsAsm,
248}
249
250impl<'test> TestCx<'test> {
251 fn run_revision(&self) {
254 if self.props.should_ice && self.config.mode != Incremental && self.config.mode != Crashes {
255 self.fatal("cannot use should-ice in a test that is not cfail");
256 }
257 match self.config.mode {
258 Pretty => self.run_pretty_test(),
259 DebugInfo => self.run_debuginfo_test(),
260 Codegen => self.run_codegen_test(),
261 Rustdoc => self.run_rustdoc_test(),
262 RustdocJson => self.run_rustdoc_json_test(),
263 CodegenUnits => self.run_codegen_units_test(),
264 Incremental => self.run_incremental_test(),
265 RunMake => self.run_rmake_test(),
266 Ui => self.run_ui_test(),
267 MirOpt => self.run_mir_opt_test(),
268 Assembly => self.run_assembly_test(),
269 RustdocJs => self.run_rustdoc_js_test(),
270 CoverageMap => self.run_coverage_map_test(), CoverageRun => self.run_coverage_run_test(), Crashes => self.run_crash_test(),
273 }
274 }
275
276 fn pass_mode(&self) -> Option<PassMode> {
277 self.props.pass_mode(self.config)
278 }
279
280 fn should_run(&self, pm: Option<PassMode>) -> WillExecute {
281 let test_should_run = match self.config.mode {
282 Ui if pm == Some(PassMode::Run) || self.props.fail_mode == Some(FailMode::Run) => true,
283 MirOpt if pm == Some(PassMode::Run) => true,
284 Ui | MirOpt => false,
285 mode => panic!("unimplemented for mode {:?}", mode),
286 };
287 if test_should_run { self.run_if_enabled() } else { WillExecute::No }
288 }
289
290 fn run_if_enabled(&self) -> WillExecute {
291 if self.config.run_enabled() { WillExecute::Yes } else { WillExecute::Disabled }
292 }
293
294 fn should_run_successfully(&self, pm: Option<PassMode>) -> bool {
295 match self.config.mode {
296 Ui | MirOpt => pm == Some(PassMode::Run),
297 mode => panic!("unimplemented for mode {:?}", mode),
298 }
299 }
300
301 fn should_compile_successfully(&self, pm: Option<PassMode>) -> bool {
302 match self.config.mode {
303 RustdocJs => true,
304 Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build),
305 Crashes => false,
306 Incremental => {
307 let revision =
308 self.revision.expect("incremental tests require a list of revisions");
309 if revision.starts_with("cpass")
310 || revision.starts_with("rpass")
311 || revision.starts_with("rfail")
312 {
313 true
314 } else if revision.starts_with("cfail") {
315 pm.is_some()
316 } else {
317 panic!("revision name must begin with cpass, rpass, rfail, or cfail");
318 }
319 }
320 mode => panic!("unimplemented for mode {:?}", mode),
321 }
322 }
323
324 fn check_if_test_should_compile(
325 &self,
326 fail_mode: Option<FailMode>,
327 pass_mode: Option<PassMode>,
328 proc_res: &ProcRes,
329 ) {
330 if self.should_compile_successfully(pass_mode) {
331 if !proc_res.status.success() {
332 match (fail_mode, pass_mode) {
333 (Some(FailMode::Build), Some(PassMode::Check)) => {
334 self.fatal_proc_rec(
336 "`build-fail` test is required to pass check build, but check build failed",
337 proc_res,
338 );
339 }
340 _ => {
341 self.fatal_proc_rec(
342 "test compilation failed although it shouldn't!",
343 proc_res,
344 );
345 }
346 }
347 }
348 } else {
349 if proc_res.status.success() {
350 {
351 self.error(&format!("{} test did not emit an error", self.config.mode));
352 if self.config.mode == crate::common::Mode::Ui {
353 println!("note: by default, ui tests are expected not to compile");
354 }
355 proc_res.fatal(None, || ());
356 };
357 }
358
359 if !self.props.dont_check_failure_status {
360 self.check_correct_failure_status(proc_res);
361 }
362 }
363 }
364
365 fn get_output(&self, proc_res: &ProcRes) -> String {
366 if self.props.check_stdout {
367 format!("{}{}", proc_res.stdout, proc_res.stderr)
368 } else {
369 proc_res.stderr.clone()
370 }
371 }
372
373 fn check_correct_failure_status(&self, proc_res: &ProcRes) {
374 let expected_status = Some(self.props.failure_status.unwrap_or(1));
375 let received_status = proc_res.status.code();
376
377 if expected_status != received_status {
378 self.fatal_proc_rec(
379 &format!(
380 "Error: expected failure status ({:?}) but received status {:?}.",
381 expected_status, received_status
382 ),
383 proc_res,
384 );
385 }
386 }
387
388 #[must_use = "caller should check whether the command succeeded"]
398 fn run_command_to_procres(&self, cmd: &mut Command) -> ProcRes {
399 let output = cmd
400 .output()
401 .unwrap_or_else(|e| self.fatal(&format!("failed to exec `{cmd:?}` because: {e}")));
402
403 let proc_res = ProcRes {
404 status: output.status,
405 stdout: String::from_utf8(output.stdout).unwrap(),
406 stderr: String::from_utf8(output.stderr).unwrap(),
407 truncated: Truncated::No,
408 cmdline: format!("{cmd:?}"),
409 };
410 self.dump_output(
411 self.config.verbose,
412 &cmd.get_program().to_string_lossy(),
413 &proc_res.stdout,
414 &proc_res.stderr,
415 );
416
417 proc_res
418 }
419
420 fn print_source(&self, read_from: ReadFrom, pretty_type: &str) -> ProcRes {
421 let aux_dir = self.aux_output_dir_name();
422 let input: &str = match read_from {
423 ReadFrom::Stdin(_) => "-",
424 ReadFrom::Path => self.testpaths.file.as_str(),
425 };
426
427 let mut rustc = Command::new(&self.config.rustc_path);
428 rustc
429 .arg(input)
430 .args(&["-Z", &format!("unpretty={}", pretty_type)])
431 .args(&["--target", &self.config.target])
432 .arg("-L")
433 .arg(&aux_dir)
434 .arg("-A")
435 .arg("internal_features")
436 .args(&self.props.compile_flags)
437 .envs(self.props.rustc_env.clone());
438 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
439
440 let src = match read_from {
441 ReadFrom::Stdin(src) => Some(src),
442 ReadFrom::Path => None,
443 };
444
445 self.compose_and_run(
446 rustc,
447 self.config.compile_lib_path.as_path(),
448 Some(aux_dir.as_path()),
449 src,
450 )
451 }
452
453 fn compare_source(&self, expected: &str, actual: &str) {
454 if expected != actual {
455 self.fatal(&format!(
456 "pretty-printed source does not match expected source\n\
457 expected:\n\
458 ------------------------------------------\n\
459 {}\n\
460 ------------------------------------------\n\
461 actual:\n\
462 ------------------------------------------\n\
463 {}\n\
464 ------------------------------------------\n\
465 diff:\n\
466 ------------------------------------------\n\
467 {}\n",
468 expected,
469 actual,
470 write_diff(expected, actual, 3),
471 ));
472 }
473 }
474
475 fn set_revision_flags(&self, cmd: &mut Command) {
476 let normalize_revision = |revision: &str| revision.to_lowercase().replace("-", "_");
479
480 if let Some(revision) = self.revision {
481 let normalized_revision = normalize_revision(revision);
482 let cfg_arg = ["--cfg", &normalized_revision];
483 let arg = format!("--cfg={normalized_revision}");
484 if self
485 .props
486 .compile_flags
487 .windows(2)
488 .any(|args| args == cfg_arg || args[0] == arg || args[1] == arg)
489 {
490 error!(
491 "redundant cfg argument `{normalized_revision}` is already created by the \
492 revision"
493 );
494 panic!("redundant cfg argument");
495 }
496 if self.config.builtin_cfg_names().contains(&normalized_revision) {
497 error!("revision `{normalized_revision}` collides with a built-in cfg");
498 panic!("revision collides with built-in cfg");
499 }
500 cmd.args(cfg_arg);
501 }
502
503 if !self.props.no_auto_check_cfg {
504 let mut check_cfg = String::with_capacity(25);
505
506 check_cfg.push_str("cfg(test,FALSE");
512 for revision in &self.props.revisions {
513 check_cfg.push(',');
514 check_cfg.push_str(&normalize_revision(revision));
515 }
516 check_cfg.push(')');
517
518 cmd.args(&["--check-cfg", &check_cfg]);
519 }
520 }
521
522 fn typecheck_source(&self, src: String) -> ProcRes {
523 let mut rustc = Command::new(&self.config.rustc_path);
524
525 let out_dir = self.output_base_name().with_extension("pretty-out");
526 remove_and_create_dir_all(&out_dir).unwrap_or_else(|e| {
527 panic!("failed to remove and recreate output directory `{out_dir}`: {e}")
528 });
529
530 let target = if self.props.force_host { &*self.config.host } else { &*self.config.target };
531
532 let aux_dir = self.aux_output_dir_name();
533
534 rustc
535 .arg("-")
536 .arg("-Zno-codegen")
537 .arg("--out-dir")
538 .arg(&out_dir)
539 .arg(&format!("--target={}", target))
540 .arg("-L")
541 .arg(&self.config.build_test_suite_root)
544 .arg("-L")
545 .arg(aux_dir)
546 .arg("-A")
547 .arg("internal_features");
548 self.set_revision_flags(&mut rustc);
549 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
550 rustc.args(&self.props.compile_flags);
551
552 self.compose_and_run_compiler(rustc, Some(src), self.testpaths)
553 }
554
555 fn maybe_add_external_args(&self, cmd: &mut Command, args: &Vec<String>) {
556 const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", "opt-level="];
561 const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", "debuginfo="];
562
563 let have_opt_flag =
567 self.props.compile_flags.iter().any(|arg| OPT_FLAGS.iter().any(|f| arg.starts_with(f)));
568 let have_debug_flag = self
569 .props
570 .compile_flags
571 .iter()
572 .any(|arg| DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)));
573
574 for arg in args {
575 if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag {
576 continue;
577 }
578 if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag {
579 continue;
580 }
581 cmd.arg(arg);
582 }
583 }
584
585 fn check_all_error_patterns(&self, output_to_check: &str, proc_res: &ProcRes) {
587 let mut missing_patterns: Vec<String> = Vec::new();
588 self.check_error_patterns(output_to_check, &mut missing_patterns);
589 self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns);
590
591 if missing_patterns.is_empty() {
592 return;
593 }
594
595 if missing_patterns.len() == 1 {
596 self.fatal_proc_rec(
597 &format!("error pattern '{}' not found!", missing_patterns[0]),
598 proc_res,
599 );
600 } else {
601 for pattern in missing_patterns {
602 self.error(&format!("error pattern '{}' not found!", pattern));
603 }
604 self.fatal_proc_rec("multiple error patterns not found", proc_res);
605 }
606 }
607
608 fn check_error_patterns(&self, output_to_check: &str, missing_patterns: &mut Vec<String>) {
609 debug!("check_error_patterns");
610 for pattern in &self.props.error_patterns {
611 if output_to_check.contains(pattern.trim()) {
612 debug!("found error pattern {}", pattern);
613 } else {
614 missing_patterns.push(pattern.to_string());
615 }
616 }
617 }
618
619 fn check_regex_error_patterns(
620 &self,
621 output_to_check: &str,
622 proc_res: &ProcRes,
623 missing_patterns: &mut Vec<String>,
624 ) {
625 debug!("check_regex_error_patterns");
626
627 for pattern in &self.props.regex_error_patterns {
628 let pattern = pattern.trim();
629 let re = match Regex::new(pattern) {
630 Ok(re) => re,
631 Err(err) => {
632 self.fatal_proc_rec(
633 &format!("invalid regex error pattern '{}': {:?}", pattern, err),
634 proc_res,
635 );
636 }
637 };
638 if re.is_match(output_to_check) {
639 debug!("found regex error pattern {}", pattern);
640 } else {
641 missing_patterns.push(pattern.to_string());
642 }
643 }
644 }
645
646 fn check_no_compiler_crash(&self, proc_res: &ProcRes, should_ice: bool) {
647 match proc_res.status.code() {
648 Some(101) if !should_ice => {
649 self.fatal_proc_rec("compiler encountered internal error", proc_res)
650 }
651 None => self.fatal_proc_rec("compiler terminated by signal", proc_res),
652 _ => (),
653 }
654 }
655
656 fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) {
657 for pat in &self.props.forbid_output {
658 if output_to_check.contains(pat) {
659 self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res);
660 }
661 }
662 }
663
664 fn check_expected_errors(&self, proc_res: &ProcRes) {
666 let expected_errors = load_errors(&self.testpaths.file, self.revision);
667 debug!(
668 "check_expected_errors: expected_errors={:?} proc_res.status={:?}",
669 expected_errors, proc_res.status
670 );
671 if proc_res.status.success() && expected_errors.iter().any(|x| x.kind == ErrorKind::Error) {
672 self.fatal_proc_rec("process did not return an error status", proc_res);
673 }
674
675 if self.props.known_bug {
676 if !expected_errors.is_empty() {
677 self.fatal_proc_rec(
678 "`known_bug` tests should not have an expected error",
679 proc_res,
680 );
681 }
682 return;
683 }
684
685 let diagnostic_file_name = if self.props.remap_src_base {
688 let mut p = Utf8PathBuf::from(FAKE_SRC_BASE);
689 p.push(&self.testpaths.relative_dir);
690 p.push(self.testpaths.file.file_name().unwrap());
691 p.to_string()
692 } else {
693 self.testpaths.file.to_string()
694 };
695
696 let expected_kinds: HashSet<_> = [ErrorKind::Error, ErrorKind::Warning]
699 .into_iter()
700 .chain(expected_errors.iter().map(|e| e.kind))
701 .collect();
702
703 let actual_errors = json::parse_output(&diagnostic_file_name, &self.get_output(proc_res))
705 .into_iter()
706 .map(|e| Error { msg: self.normalize_output(&e.msg, &[]), ..e });
707
708 let mut unexpected = Vec::new();
709 let mut unimportant = Vec::new();
710 let mut found = vec![false; expected_errors.len()];
711 for actual_error in actual_errors {
712 for pattern in &self.props.error_patterns {
713 let pattern = pattern.trim();
714 if actual_error.msg.contains(pattern) {
715 let q = if actual_error.line_num.is_none() { "?" } else { "" };
716 self.fatal(&format!(
717 "error pattern '{pattern}' is found in structured \
718 diagnostics, use `//~{q} {} {pattern}` instead",
719 actual_error.kind,
720 ));
721 }
722 }
723
724 let opt_index =
725 expected_errors.iter().enumerate().position(|(index, expected_error)| {
726 !found[index]
727 && actual_error.line_num == expected_error.line_num
728 && actual_error.kind == expected_error.kind
729 && actual_error.msg.contains(&expected_error.msg)
730 });
731
732 match opt_index {
733 Some(index) => {
734 assert!(!found[index]);
736 found[index] = true;
737 }
738
739 None => {
740 if actual_error.require_annotation
741 && expected_kinds.contains(&actual_error.kind)
742 && !self.props.dont_require_annotations.contains(&actual_error.kind)
743 {
744 unexpected.push(actual_error);
745 } else {
746 unimportant.push(actual_error);
747 }
748 }
749 }
750 }
751
752 let mut not_found = Vec::new();
753 for (index, expected_error) in expected_errors.iter().enumerate() {
755 if !found[index] {
756 not_found.push(expected_error);
757 }
758 }
759
760 if !unexpected.is_empty() || !not_found.is_empty() {
761 self.error(&format!(
762 "{} unexpected diagnostics reported, {} expected diagnostics not reported",
763 unexpected.len(),
764 not_found.len()
765 ));
766
767 let file_name = self
770 .testpaths
771 .file
772 .strip_prefix(self.config.src_root.as_str())
773 .unwrap_or(&self.testpaths.file)
774 .to_string()
775 .replace(r"\", "/");
776 let line_str = |e: &Error| {
777 let line_num = e.line_num.map_or("?".to_string(), |line_num| line_num.to_string());
778 let opt_col_num = match e.column_num {
780 Some(col_num) if line_num != "?" => format!(":{col_num}"),
781 _ => "".to_string(),
782 };
783 format!("{file_name}:{line_num}{opt_col_num}")
784 };
785 let print_error = |e| println!("{}: {}: {}", line_str(e), e.kind, e.msg.cyan());
786 let push_suggestion =
787 |suggestions: &mut Vec<_>, e: &Error, kind, line, msg, color, rank| {
788 let mut ret = String::new();
789 if kind {
790 ret += &format!("{} {}", "with kind".color(color), e.kind);
791 }
792 if line {
793 if !ret.is_empty() {
794 ret.push(' ');
795 }
796 ret += &format!("{} {}", "on line".color(color), line_str(e));
797 }
798 if msg {
799 if !ret.is_empty() {
800 ret.push(' ');
801 }
802 ret += &format!("{} {}", "with message".color(color), e.msg.cyan());
803 }
804 suggestions.push((ret, rank));
805 };
806 let show_suggestions = |mut suggestions: Vec<_>, prefix: &str, color| {
807 suggestions.sort_by_key(|(_, rank)| *rank);
809 if let Some(&(_, top_rank)) = suggestions.first() {
810 for (suggestion, rank) in suggestions {
811 if rank == top_rank {
812 println!(" {} {suggestion}", prefix.color(color));
813 }
814 }
815 }
816 };
817
818 if !unexpected.is_empty() {
825 let header = "--- reported in JSON output but not expected in test file ---";
826 println!("{}", header.green());
827 for error in &unexpected {
828 print_error(error);
829 let mut suggestions = Vec::new();
830 for candidate in ¬_found {
831 let mut push_red_suggestion = |line, msg, rank| {
832 push_suggestion(
833 &mut suggestions,
834 candidate,
835 candidate.kind != error.kind,
836 line,
837 msg,
838 Color::Red,
839 rank,
840 )
841 };
842 if error.msg.contains(&candidate.msg) {
843 push_red_suggestion(candidate.line_num != error.line_num, false, 0);
844 } else if candidate.line_num.is_some()
845 && candidate.line_num == error.line_num
846 {
847 push_red_suggestion(false, true, 1);
848 }
849 }
850
851 show_suggestions(suggestions, "expected", Color::Red);
852 }
853 println!("{}", "---".green());
854 }
855 if !not_found.is_empty() {
856 let header = "--- expected in test file but not reported in JSON output ---";
857 println!("{}", header.red());
858 for error in ¬_found {
859 print_error(error);
860 let mut suggestions = Vec::new();
861 for candidate in unexpected.iter().chain(&unimportant) {
862 let mut push_green_suggestion = |line, msg, rank| {
863 push_suggestion(
864 &mut suggestions,
865 candidate,
866 candidate.kind != error.kind,
867 line,
868 msg,
869 Color::Green,
870 rank,
871 )
872 };
873 if candidate.msg.contains(&error.msg) {
874 push_green_suggestion(candidate.line_num != error.line_num, false, 0);
875 } else if candidate.line_num.is_some()
876 && candidate.line_num == error.line_num
877 {
878 push_green_suggestion(false, true, 1);
879 }
880 }
881
882 show_suggestions(suggestions, "reported", Color::Green);
883 }
884 println!("{}", "---".red());
885 }
886 panic!(
887 "errors differ from expected\nstatus: {}\ncommand: {}\n",
888 proc_res.status, proc_res.cmdline
889 );
890 }
891 }
892
893 fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {
894 match (pm, self.props.fail_mode, self.config.mode) {
895 (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), Ui) => Emit::Metadata,
896 _ => Emit::None,
897 }
898 }
899
900 fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes {
901 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), Vec::new())
902 }
903
904 fn compile_test_with_passes(
905 &self,
906 will_execute: WillExecute,
907 emit: Emit,
908 passes: Vec<String>,
909 ) -> ProcRes {
910 self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), passes)
911 }
912
913 fn compile_test_general(
914 &self,
915 will_execute: WillExecute,
916 emit: Emit,
917 local_pm: Option<PassMode>,
918 passes: Vec<String>,
919 ) -> ProcRes {
920 let output_file = match will_execute {
922 WillExecute::Yes => TargetLocation::ThisFile(self.make_exe_name()),
923 WillExecute::No | WillExecute::Disabled => {
924 TargetLocation::ThisDirectory(self.output_base_dir())
925 }
926 };
927
928 let allow_unused = match self.config.mode {
929 Ui => {
930 if !self.is_rustdoc()
936 && local_pm != Some(PassMode::Run)
940 {
941 AllowUnused::Yes
942 } else {
943 AllowUnused::No
944 }
945 }
946 _ => AllowUnused::No,
947 };
948
949 let rustc = self.make_compile_args(
950 &self.testpaths.file,
951 output_file,
952 emit,
953 allow_unused,
954 LinkToAux::Yes,
955 passes,
956 );
957
958 self.compose_and_run_compiler(rustc, None, self.testpaths)
959 }
960
961 fn document(&self, root_out_dir: &Utf8Path, root_testpaths: &TestPaths) -> ProcRes {
964 if self.props.build_aux_docs {
965 for rel_ab in &self.props.aux.builds {
966 let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
967 let props_for_aux =
968 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
969 let aux_cx = TestCx {
970 config: self.config,
971 props: &props_for_aux,
972 testpaths: &aux_testpaths,
973 revision: self.revision,
974 };
975 create_dir_all(aux_cx.output_base_dir()).unwrap();
977 let auxres = aux_cx.document(&root_out_dir, root_testpaths);
980 if !auxres.status.success() {
981 return auxres;
982 }
983 }
984 }
985
986 let aux_dir = self.aux_output_dir_name();
987
988 let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed");
989
990 let out_dir: Cow<'_, Utf8Path> = if self.props.unique_doc_out_dir {
993 let file_name = self.testpaths.file.file_stem().expect("file name should not be empty");
994 let out_dir = Utf8PathBuf::from_iter([
995 root_out_dir,
996 Utf8Path::new("docs"),
997 Utf8Path::new(file_name),
998 Utf8Path::new("doc"),
999 ]);
1000 create_dir_all(&out_dir).unwrap();
1001 Cow::Owned(out_dir)
1002 } else {
1003 Cow::Borrowed(root_out_dir)
1004 };
1005
1006 let mut rustdoc = Command::new(rustdoc_path);
1007 let current_dir = output_base_dir(self.config, root_testpaths, self.safe_revision());
1008 rustdoc.current_dir(current_dir);
1009 rustdoc
1010 .arg("-L")
1011 .arg(self.config.run_lib_path.as_path())
1012 .arg("-L")
1013 .arg(aux_dir)
1014 .arg("-o")
1015 .arg(out_dir.as_ref())
1016 .arg("--deny")
1017 .arg("warnings")
1018 .arg(&self.testpaths.file)
1019 .arg("-A")
1020 .arg("internal_features")
1021 .args(&self.props.compile_flags)
1022 .args(&self.props.doc_flags);
1023
1024 if self.config.mode == RustdocJson {
1025 rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
1026 }
1027
1028 if let Some(ref linker) = self.config.target_linker {
1029 rustdoc.arg(format!("-Clinker={}", linker));
1030 }
1031
1032 self.compose_and_run_compiler(rustdoc, None, root_testpaths)
1033 }
1034
1035 fn exec_compiled_test(&self) -> ProcRes {
1036 self.exec_compiled_test_general(&[], true)
1037 }
1038
1039 fn exec_compiled_test_general(
1040 &self,
1041 env_extra: &[(&str, &str)],
1042 delete_after_success: bool,
1043 ) -> ProcRes {
1044 let prepare_env = |cmd: &mut Command| {
1045 for (key, val) in &self.props.exec_env {
1046 cmd.env(key, val);
1047 }
1048 for (key, val) in env_extra {
1049 cmd.env(key, val);
1050 }
1051
1052 for key in &self.props.unset_exec_env {
1053 cmd.env_remove(key);
1054 }
1055 };
1056
1057 let proc_res = match &*self.config.target {
1058 _ if self.config.remote_test_client.is_some() => {
1075 let aux_dir = self.aux_output_dir_name();
1076 let ProcArgs { prog, args } = self.make_run_args();
1077 let mut support_libs = Vec::new();
1078 if let Ok(entries) = aux_dir.read_dir() {
1079 for entry in entries {
1080 let entry = entry.unwrap();
1081 if !entry.path().is_file() {
1082 continue;
1083 }
1084 support_libs.push(entry.path());
1085 }
1086 }
1087 let mut test_client =
1088 Command::new(self.config.remote_test_client.as_ref().unwrap());
1089 test_client
1090 .args(&["run", &support_libs.len().to_string()])
1091 .arg(&prog)
1092 .args(support_libs)
1093 .args(args);
1094
1095 prepare_env(&mut test_client);
1096
1097 self.compose_and_run(
1098 test_client,
1099 self.config.run_lib_path.as_path(),
1100 Some(aux_dir.as_path()),
1101 None,
1102 )
1103 }
1104 _ if self.config.target.contains("vxworks") => {
1105 let aux_dir = self.aux_output_dir_name();
1106 let ProcArgs { prog, args } = self.make_run_args();
1107 let mut wr_run = Command::new("wr-run");
1108 wr_run.args(&[&prog]).args(args);
1109
1110 prepare_env(&mut wr_run);
1111
1112 self.compose_and_run(
1113 wr_run,
1114 self.config.run_lib_path.as_path(),
1115 Some(aux_dir.as_path()),
1116 None,
1117 )
1118 }
1119 _ => {
1120 let aux_dir = self.aux_output_dir_name();
1121 let ProcArgs { prog, args } = self.make_run_args();
1122 let mut program = Command::new(&prog);
1123 program.args(args).current_dir(&self.output_base_dir());
1124
1125 prepare_env(&mut program);
1126
1127 self.compose_and_run(
1128 program,
1129 self.config.run_lib_path.as_path(),
1130 Some(aux_dir.as_path()),
1131 None,
1132 )
1133 }
1134 };
1135
1136 if delete_after_success && proc_res.status.success() {
1137 let _ = fs::remove_file(self.make_exe_name());
1140 }
1141
1142 proc_res
1143 }
1144
1145 fn compute_aux_test_paths(&self, of: &TestPaths, rel_ab: &str) -> TestPaths {
1148 let test_ab =
1149 of.file.parent().expect("test file path has no parent").join("auxiliary").join(rel_ab);
1150 if !test_ab.exists() {
1151 self.fatal(&format!("aux-build `{}` source not found", test_ab))
1152 }
1153
1154 TestPaths {
1155 file: test_ab,
1156 relative_dir: of
1157 .relative_dir
1158 .join(self.output_testname_unique())
1159 .join("auxiliary")
1160 .join(rel_ab)
1161 .parent()
1162 .expect("aux-build path has no parent")
1163 .to_path_buf(),
1164 }
1165 }
1166
1167 fn is_vxworks_pure_static(&self) -> bool {
1168 if self.config.target.contains("vxworks") {
1169 match env::var("RUST_VXWORKS_TEST_DYLINK") {
1170 Ok(s) => s != "1",
1171 _ => true,
1172 }
1173 } else {
1174 false
1175 }
1176 }
1177
1178 fn is_vxworks_pure_dynamic(&self) -> bool {
1179 self.config.target.contains("vxworks") && !self.is_vxworks_pure_static()
1180 }
1181
1182 fn has_aux_dir(&self) -> bool {
1183 !self.props.aux.builds.is_empty()
1184 || !self.props.aux.crates.is_empty()
1185 || !self.props.aux.proc_macros.is_empty()
1186 }
1187
1188 fn aux_output_dir(&self) -> Utf8PathBuf {
1189 let aux_dir = self.aux_output_dir_name();
1190
1191 if !self.props.aux.builds.is_empty() {
1192 remove_and_create_dir_all(&aux_dir).unwrap_or_else(|e| {
1193 panic!("failed to remove and recreate output directory `{aux_dir}`: {e}")
1194 });
1195 }
1196
1197 if !self.props.aux.bins.is_empty() {
1198 let aux_bin_dir = self.aux_bin_output_dir_name();
1199 remove_and_create_dir_all(&aux_dir).unwrap_or_else(|e| {
1200 panic!("failed to remove and recreate output directory `{aux_dir}`: {e}")
1201 });
1202 remove_and_create_dir_all(&aux_bin_dir).unwrap_or_else(|e| {
1203 panic!("failed to remove and recreate output directory `{aux_bin_dir}`: {e}")
1204 });
1205 }
1206
1207 aux_dir
1208 }
1209
1210 fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Utf8Path, rustc: &mut Command) {
1211 for rel_ab in &self.props.aux.builds {
1212 self.build_auxiliary(of, rel_ab, &aux_dir, None);
1213 }
1214
1215 for rel_ab in &self.props.aux.bins {
1216 self.build_auxiliary(of, rel_ab, &aux_dir, Some(AuxType::Bin));
1217 }
1218
1219 let path_to_crate_name = |path: &str| -> String {
1220 path.rsplit_once('/')
1221 .map_or(path, |(_, tail)| tail)
1222 .trim_end_matches(".rs")
1223 .replace('-', "_")
1224 };
1225
1226 let add_extern =
1227 |rustc: &mut Command, aux_name: &str, aux_path: &str, aux_type: AuxType| {
1228 let lib_name = get_lib_name(&path_to_crate_name(aux_path), aux_type);
1229 if let Some(lib_name) = lib_name {
1230 rustc.arg("--extern").arg(format!("{}={}/{}", aux_name, aux_dir, lib_name));
1231 }
1232 };
1233
1234 for (aux_name, aux_path) in &self.props.aux.crates {
1235 let aux_type = self.build_auxiliary(of, &aux_path, &aux_dir, None);
1236 add_extern(rustc, aux_name, aux_path, aux_type);
1237 }
1238
1239 for proc_macro in &self.props.aux.proc_macros {
1240 self.build_auxiliary(of, proc_macro, &aux_dir, Some(AuxType::ProcMacro));
1241 let crate_name = path_to_crate_name(proc_macro);
1242 add_extern(rustc, &crate_name, proc_macro, AuxType::ProcMacro);
1243 }
1244
1245 if let Some(aux_file) = &self.props.aux.codegen_backend {
1248 let aux_type = self.build_auxiliary(of, aux_file, aux_dir, None);
1249 if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
1250 let lib_path = aux_dir.join(&lib_name);
1251 rustc.arg(format!("-Zcodegen-backend={}", lib_path));
1252 }
1253 }
1254 }
1255
1256 fn compose_and_run_compiler(
1259 &self,
1260 mut rustc: Command,
1261 input: Option<String>,
1262 root_testpaths: &TestPaths,
1263 ) -> ProcRes {
1264 if self.props.add_core_stubs {
1265 let minicore_path = self.build_minicore();
1266 rustc.arg("--extern");
1267 rustc.arg(&format!("minicore={}", minicore_path));
1268 }
1269
1270 let aux_dir = self.aux_output_dir();
1271 self.build_all_auxiliary(root_testpaths, &aux_dir, &mut rustc);
1272
1273 rustc.envs(self.props.rustc_env.clone());
1274 self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
1275 self.compose_and_run(
1276 rustc,
1277 self.config.compile_lib_path.as_path(),
1278 Some(aux_dir.as_path()),
1279 input,
1280 )
1281 }
1282
1283 fn build_minicore(&self) -> Utf8PathBuf {
1286 let output_file_path = self.output_base_dir().join("libminicore.rlib");
1287 let mut rustc = self.make_compile_args(
1288 &self.config.minicore_path,
1289 TargetLocation::ThisFile(output_file_path.clone()),
1290 Emit::None,
1291 AllowUnused::Yes,
1292 LinkToAux::No,
1293 vec![],
1294 );
1295
1296 rustc.args(&["--crate-type", "rlib"]);
1297 rustc.arg("-Cpanic=abort");
1298
1299 let res = self.compose_and_run(rustc, self.config.compile_lib_path.as_path(), None, None);
1300 if !res.status.success() {
1301 self.fatal_proc_rec(
1302 &format!("auxiliary build of {} failed to compile: ", self.config.minicore_path),
1303 &res,
1304 );
1305 }
1306
1307 output_file_path
1308 }
1309
1310 fn build_auxiliary(
1314 &self,
1315 of: &TestPaths,
1316 source_path: &str,
1317 aux_dir: &Utf8Path,
1318 aux_type: Option<AuxType>,
1319 ) -> AuxType {
1320 let aux_testpaths = self.compute_aux_test_paths(of, source_path);
1321 let mut aux_props =
1322 self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config);
1323 if aux_type == Some(AuxType::ProcMacro) {
1324 aux_props.force_host = true;
1325 }
1326 let mut aux_dir = aux_dir.to_path_buf();
1327 if aux_type == Some(AuxType::Bin) {
1328 aux_dir.push("bin");
1332 }
1333 let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1334 let aux_cx = TestCx {
1335 config: self.config,
1336 props: &aux_props,
1337 testpaths: &aux_testpaths,
1338 revision: self.revision,
1339 };
1340 create_dir_all(aux_cx.output_base_dir()).unwrap();
1342 let input_file = &aux_testpaths.file;
1343 let mut aux_rustc = aux_cx.make_compile_args(
1344 input_file,
1345 aux_output,
1346 Emit::None,
1347 AllowUnused::No,
1348 LinkToAux::No,
1349 Vec::new(),
1350 );
1351 aux_cx.build_all_auxiliary(of, &aux_dir, &mut aux_rustc);
1352
1353 aux_rustc.envs(aux_props.rustc_env.clone());
1354 for key in &aux_props.unset_rustc_env {
1355 aux_rustc.env_remove(key);
1356 }
1357
1358 let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1359 (AuxType::Bin, Some("bin"))
1360 } else if aux_type == Some(AuxType::ProcMacro) {
1361 (AuxType::ProcMacro, Some("proc-macro"))
1362 } else if aux_type.is_some() {
1363 panic!("aux_type {aux_type:?} not expected");
1364 } else if aux_props.no_prefer_dynamic {
1365 (AuxType::Dylib, None)
1366 } else if self.config.target.contains("emscripten")
1367 || (self.config.target.contains("musl")
1368 && !aux_props.force_host
1369 && !self.config.host.contains("musl"))
1370 || self.config.target.contains("wasm32")
1371 || self.config.target.contains("nvptx")
1372 || self.is_vxworks_pure_static()
1373 || self.config.target.contains("bpf")
1374 || !self.config.target_cfg().dynamic_linking
1375 || matches!(self.config.mode, CoverageMap | CoverageRun)
1376 {
1377 (AuxType::Lib, Some("lib"))
1391 } else {
1392 (AuxType::Dylib, Some("dylib"))
1393 };
1394
1395 if let Some(crate_type) = crate_type {
1396 aux_rustc.args(&["--crate-type", crate_type]);
1397 }
1398
1399 if aux_type == AuxType::ProcMacro {
1400 aux_rustc.args(&["--extern", "proc_macro"]);
1402 }
1403
1404 aux_rustc.arg("-L").arg(&aux_dir);
1405
1406 let auxres = aux_cx.compose_and_run(
1407 aux_rustc,
1408 aux_cx.config.compile_lib_path.as_path(),
1409 Some(aux_dir.as_path()),
1410 None,
1411 );
1412 if !auxres.status.success() {
1413 self.fatal_proc_rec(
1414 &format!("auxiliary build of {} failed to compile: ", aux_testpaths.file),
1415 &auxres,
1416 );
1417 }
1418 aux_type
1419 }
1420
1421 fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1422 let mut filter_paths_from_len = Vec::new();
1423 let mut add_path = |path: &Utf8Path| {
1424 let path = path.to_string();
1425 let windows = path.replace("\\", "\\\\");
1426 if windows != path {
1427 filter_paths_from_len.push(windows);
1428 }
1429 filter_paths_from_len.push(path);
1430 };
1431
1432 add_path(&self.config.src_test_suite_root);
1438 add_path(&self.config.build_test_suite_root);
1439
1440 read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1441 }
1442
1443 fn compose_and_run(
1444 &self,
1445 mut command: Command,
1446 lib_path: &Utf8Path,
1447 aux_path: Option<&Utf8Path>,
1448 input: Option<String>,
1449 ) -> ProcRes {
1450 let cmdline = {
1451 let cmdline = self.make_cmdline(&command, lib_path);
1452 logv(self.config, format!("executing {}", cmdline));
1453 cmdline
1454 };
1455
1456 command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1457
1458 add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1461
1462 let mut child = disable_error_reporting(|| command.spawn())
1463 .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1464 if let Some(input) = input {
1465 child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1466 }
1467
1468 let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1469
1470 let result = ProcRes {
1471 status,
1472 stdout: String::from_utf8_lossy(&stdout).into_owned(),
1473 stderr: String::from_utf8_lossy(&stderr).into_owned(),
1474 truncated,
1475 cmdline,
1476 };
1477
1478 self.dump_output(
1479 self.config.verbose,
1480 &command.get_program().to_string_lossy(),
1481 &result.stdout,
1482 &result.stderr,
1483 );
1484
1485 result
1486 }
1487
1488 fn is_rustdoc(&self) -> bool {
1489 matches!(self.config.suite.as_str(), "rustdoc-ui" | "rustdoc-js" | "rustdoc-json")
1490 }
1491
1492 fn make_compile_args(
1493 &self,
1494 input_file: &Utf8Path,
1495 output_file: TargetLocation,
1496 emit: Emit,
1497 allow_unused: AllowUnused,
1498 link_to_aux: LinkToAux,
1499 passes: Vec<String>, ) -> Command {
1501 let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
1502 let is_rustdoc = self.is_rustdoc() && !is_aux;
1503 let mut rustc = if !is_rustdoc {
1504 Command::new(&self.config.rustc_path)
1505 } else {
1506 Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1507 };
1508 rustc.arg(input_file);
1509
1510 rustc.arg("-Zthreads=1");
1512
1513 rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1522 rustc.arg("-Ztranslate-remapped-path-to-local-path=no");
1523
1524 rustc.arg("-Z").arg(format!(
1529 "ignore-directory-in-diagnostics-source-blocks={}",
1530 home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1531 ));
1532 rustc.arg("-Z").arg(format!(
1534 "ignore-directory-in-diagnostics-source-blocks={}",
1535 self.config.src_root.join("vendor"),
1536 ));
1537
1538 if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1542 && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1543 {
1544 rustc.arg("--sysroot").arg(&self.config.sysroot_base);
1546 }
1547
1548 let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1550
1551 if !custom_target {
1552 let target =
1553 if self.props.force_host { &*self.config.host } else { &*self.config.target };
1554
1555 rustc.arg(&format!("--target={}", target));
1556 }
1557 self.set_revision_flags(&mut rustc);
1558
1559 if !is_rustdoc {
1560 if let Some(ref incremental_dir) = self.props.incremental_dir {
1561 rustc.args(&["-C", &format!("incremental={}", incremental_dir)]);
1562 rustc.args(&["-Z", "incremental-verify-ich"]);
1563 }
1564
1565 if self.config.mode == CodegenUnits {
1566 rustc.args(&["-Z", "human_readable_cgu_names"]);
1567 }
1568 }
1569
1570 if self.config.optimize_tests && !is_rustdoc {
1571 match self.config.mode {
1572 Ui => {
1573 if self.config.optimize_tests
1578 && self.props.pass_mode(&self.config) == Some(PassMode::Run)
1579 && !self
1580 .props
1581 .compile_flags
1582 .iter()
1583 .any(|arg| arg == "-O" || arg.contains("opt-level"))
1584 {
1585 rustc.arg("-O");
1586 }
1587 }
1588 DebugInfo => { }
1589 CoverageMap | CoverageRun => {
1590 }
1595 _ => {
1596 rustc.arg("-O");
1597 }
1598 }
1599 }
1600
1601 let set_mir_dump_dir = |rustc: &mut Command| {
1602 let mir_dump_dir = self.output_base_dir();
1603 let mut dir_opt = "-Zdump-mir-dir=".to_string();
1604 dir_opt.push_str(mir_dump_dir.as_str());
1605 debug!("dir_opt: {:?}", dir_opt);
1606 rustc.arg(dir_opt);
1607 };
1608
1609 match self.config.mode {
1610 Incremental => {
1611 if self.props.error_patterns.is_empty()
1615 && self.props.regex_error_patterns.is_empty()
1616 {
1617 rustc.args(&["--error-format", "json"]);
1618 rustc.args(&["--json", "future-incompat"]);
1619 }
1620 rustc.arg("-Zui-testing");
1621 rustc.arg("-Zdeduplicate-diagnostics=no");
1622 }
1623 Ui => {
1624 if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1625 rustc.args(&["--error-format", "json"]);
1626 rustc.args(&["--json", "future-incompat"]);
1627 }
1628 rustc.arg("-Ccodegen-units=1");
1629 rustc.arg("-Zui-testing");
1631 rustc.arg("-Zdeduplicate-diagnostics=no");
1632 rustc.arg("-Zwrite-long-types-to-disk=no");
1633 rustc.arg("-Cstrip=debuginfo");
1635 }
1636 MirOpt => {
1637 let zdump_arg = if !passes.is_empty() {
1641 format!("-Zdump-mir={}", passes.join(" | "))
1642 } else {
1643 "-Zdump-mir=all".to_string()
1644 };
1645
1646 rustc.args(&[
1647 "-Copt-level=1",
1648 &zdump_arg,
1649 "-Zvalidate-mir",
1650 "-Zlint-mir",
1651 "-Zdump-mir-exclude-pass-number",
1652 "-Zmir-include-spans=false", "--crate-type=rlib",
1654 ]);
1655 if let Some(pass) = &self.props.mir_unit_test {
1656 rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1657 } else {
1658 rustc.args(&[
1659 "-Zmir-opt-level=4",
1660 "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1661 ]);
1662 }
1663
1664 set_mir_dump_dir(&mut rustc);
1665 }
1666 CoverageMap => {
1667 rustc.arg("-Cinstrument-coverage");
1668 rustc.arg("-Zno-profiler-runtime");
1671 rustc.arg("-Copt-level=2");
1675 }
1676 CoverageRun => {
1677 rustc.arg("-Cinstrument-coverage");
1678 rustc.arg("-Copt-level=2");
1682 }
1683 Assembly | Codegen => {
1684 rustc.arg("-Cdebug-assertions=no");
1685 }
1686 Crashes => {
1687 set_mir_dump_dir(&mut rustc);
1688 }
1689 CodegenUnits => {
1690 rustc.arg("-Zprint-mono-items");
1691 }
1692 Pretty | DebugInfo | Rustdoc | RustdocJson | RunMake | RustdocJs => {
1693 }
1695 }
1696
1697 if self.props.remap_src_base {
1698 rustc.arg(format!(
1699 "--remap-path-prefix={}={}",
1700 self.config.src_test_suite_root, FAKE_SRC_BASE,
1701 ));
1702 }
1703
1704 match emit {
1705 Emit::None => {}
1706 Emit::Metadata if is_rustdoc => {}
1707 Emit::Metadata => {
1708 rustc.args(&["--emit", "metadata"]);
1709 }
1710 Emit::LlvmIr => {
1711 rustc.args(&["--emit", "llvm-ir"]);
1712 }
1713 Emit::Mir => {
1714 rustc.args(&["--emit", "mir"]);
1715 }
1716 Emit::Asm => {
1717 rustc.args(&["--emit", "asm"]);
1718 }
1719 Emit::LinkArgsAsm => {
1720 rustc.args(&["-Clink-args=--emit=asm"]);
1721 }
1722 }
1723
1724 if !is_rustdoc {
1725 if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1726 } else if !self.props.no_prefer_dynamic {
1728 rustc.args(&["-C", "prefer-dynamic"]);
1729 }
1730 }
1731
1732 match output_file {
1733 _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1736 TargetLocation::ThisFile(path) => {
1737 rustc.arg("-o").arg(path);
1738 }
1739 TargetLocation::ThisDirectory(path) => {
1740 if is_rustdoc {
1741 rustc.arg("-o").arg(path);
1743 } else {
1744 rustc.arg("--out-dir").arg(path);
1745 }
1746 }
1747 }
1748
1749 match self.config.compare_mode {
1750 Some(CompareMode::Polonius) => {
1751 rustc.args(&["-Zpolonius"]);
1752 }
1753 Some(CompareMode::NextSolver) => {
1754 rustc.args(&["-Znext-solver"]);
1755 }
1756 Some(CompareMode::NextSolverCoherence) => {
1757 rustc.args(&["-Znext-solver=coherence"]);
1758 }
1759 Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1760 rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1761 }
1762 Some(CompareMode::SplitDwarf) => {
1763 rustc.args(&["-Csplit-debuginfo=unpacked"]);
1764 }
1765 Some(CompareMode::SplitDwarfSingle) => {
1766 rustc.args(&["-Csplit-debuginfo=packed"]);
1767 }
1768 None => {}
1769 }
1770
1771 if let AllowUnused::Yes = allow_unused {
1774 rustc.args(&["-A", "unused"]);
1775 }
1776
1777 rustc.args(&["-A", "internal_features"]);
1779
1780 if self.props.force_host {
1781 self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1782 if !is_rustdoc {
1783 if let Some(ref linker) = self.config.host_linker {
1784 rustc.arg(format!("-Clinker={}", linker));
1785 }
1786 }
1787 } else {
1788 self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1789 if !is_rustdoc {
1790 if let Some(ref linker) = self.config.target_linker {
1791 rustc.arg(format!("-Clinker={}", linker));
1792 }
1793 }
1794 }
1795
1796 if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1798 rustc.arg("-Ctarget-feature=-crt-static");
1799 }
1800
1801 if let LinkToAux::Yes = link_to_aux {
1802 if self.has_aux_dir() {
1805 rustc.arg("-L").arg(self.aux_output_dir_name());
1806 }
1807 }
1808
1809 rustc.args(&self.props.compile_flags);
1810
1811 if self.props.add_core_stubs {
1820 rustc.arg("-Cpanic=abort");
1821 rustc.arg("-Cforce-unwind-tables=yes");
1822 }
1823
1824 rustc
1825 }
1826
1827 fn make_exe_name(&self) -> Utf8PathBuf {
1828 let mut f = self.output_base_dir().join("a");
1833 if self.config.target.contains("emscripten") {
1835 f = f.with_extra_extension("js");
1836 } else if self.config.target.starts_with("wasm") {
1837 f = f.with_extra_extension("wasm");
1838 } else if self.config.target.contains("spirv") {
1839 f = f.with_extra_extension("spv");
1840 } else if !env::consts::EXE_SUFFIX.is_empty() {
1841 f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1842 }
1843 f
1844 }
1845
1846 fn make_run_args(&self) -> ProcArgs {
1847 let mut args = self.split_maybe_args(&self.config.runner);
1850
1851 let exe_file = self.make_exe_name();
1852
1853 args.push(exe_file.into_os_string());
1854
1855 args.extend(self.props.run_flags.iter().map(OsString::from));
1857
1858 let prog = args.remove(0);
1859 ProcArgs { prog, args }
1860 }
1861
1862 fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1863 match *argstr {
1864 Some(ref s) => s
1865 .split(' ')
1866 .filter_map(|s| {
1867 if s.chars().all(|c| c.is_whitespace()) {
1868 None
1869 } else {
1870 Some(OsString::from(s))
1871 }
1872 })
1873 .collect(),
1874 None => Vec::new(),
1875 }
1876 }
1877
1878 fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
1879 use crate::util;
1880
1881 if cfg!(unix) {
1883 format!("{:?}", command)
1884 } else {
1885 fn lib_path_cmd_prefix(path: &str) -> String {
1888 format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1889 }
1890
1891 format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
1892 }
1893 }
1894
1895 fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1896 let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1897
1898 self.dump_output_file(out, &format!("{}out", revision));
1899 self.dump_output_file(err, &format!("{}err", revision));
1900
1901 if !print_output {
1902 return;
1903 }
1904
1905 let path = Utf8Path::new(proc_name);
1906 let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1907 String::from_iter(
1908 path.parent()
1909 .unwrap()
1910 .file_name()
1911 .into_iter()
1912 .chain(Some("/"))
1913 .chain(path.file_name()),
1914 )
1915 } else {
1916 path.file_name().unwrap().into()
1917 };
1918 println!("------{proc_name} stdout------------------------------");
1919 println!("{}", out);
1920 println!("------{proc_name} stderr------------------------------");
1921 println!("{}", err);
1922 println!("------------------------------------------");
1923 }
1924
1925 fn dump_output_file(&self, out: &str, extension: &str) {
1926 let outfile = self.make_out_name(extension);
1927 fs::write(outfile.as_std_path(), out)
1928 .unwrap_or_else(|err| panic!("failed to write {outfile}: {err:?}"));
1929 }
1930
1931 fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
1934 self.output_base_name().with_extension(extension)
1935 }
1936
1937 fn aux_output_dir_name(&self) -> Utf8PathBuf {
1940 self.output_base_dir()
1941 .join("auxiliary")
1942 .with_extra_extension(self.config.mode.aux_dir_disambiguator())
1943 }
1944
1945 fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
1948 self.aux_output_dir_name().join("bin")
1949 }
1950
1951 fn output_testname_unique(&self) -> Utf8PathBuf {
1953 output_testname_unique(self.config, self.testpaths, self.safe_revision())
1954 }
1955
1956 fn safe_revision(&self) -> Option<&str> {
1959 if self.config.mode == Incremental { None } else { self.revision }
1960 }
1961
1962 fn output_base_dir(&self) -> Utf8PathBuf {
1966 output_base_dir(self.config, self.testpaths, self.safe_revision())
1967 }
1968
1969 fn output_base_name(&self) -> Utf8PathBuf {
1973 output_base_name(self.config, self.testpaths, self.safe_revision())
1974 }
1975
1976 fn error(&self, err: &str) {
1977 match self.revision {
1978 Some(rev) => println!("\nerror in revision `{}`: {}", rev, err),
1979 None => println!("\nerror: {}", err),
1980 }
1981 }
1982
1983 #[track_caller]
1984 fn fatal(&self, err: &str) -> ! {
1985 self.error(err);
1986 error!("fatal error, panic: {:?}", err);
1987 panic!("fatal error");
1988 }
1989
1990 fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
1991 self.error(err);
1992 proc_res.fatal(None, || ());
1993 }
1994
1995 fn fatal_proc_rec_with_ctx(
1996 &self,
1997 err: &str,
1998 proc_res: &ProcRes,
1999 on_failure: impl FnOnce(Self),
2000 ) -> ! {
2001 self.error(err);
2002 proc_res.fatal(None, || on_failure(*self));
2003 }
2004
2005 fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
2008 let output_path = self.output_base_name().with_extension("ll");
2009 let input_file = &self.testpaths.file;
2010 let rustc = self.make_compile_args(
2011 input_file,
2012 TargetLocation::ThisFile(output_path.clone()),
2013 Emit::LlvmIr,
2014 AllowUnused::No,
2015 LinkToAux::Yes,
2016 Vec::new(),
2017 );
2018
2019 let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
2020 (proc_res, output_path)
2021 }
2022
2023 fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
2024 let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
2025 filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
2026
2027 filecheck.arg("--check-prefix=CHECK");
2029
2030 if let Some(rev) = self.revision {
2038 filecheck.arg("--check-prefix").arg(rev);
2039 }
2040
2041 filecheck.arg("--allow-unused-prefixes");
2045
2046 filecheck.args(&["--dump-input-context", "100"]);
2048
2049 filecheck.args(&self.props.filecheck_flags);
2051
2052 self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
2054 }
2055
2056 fn charset() -> &'static str {
2057 if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
2059 }
2060
2061 fn compare_to_default_rustdoc(&mut self, out_dir: &Utf8Path) {
2062 if !self.config.has_html_tidy {
2063 return;
2064 }
2065 println!("info: generating a diff against nightly rustdoc");
2066
2067 let suffix =
2068 self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2069 let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2070 remove_and_create_dir_all(&compare_dir).unwrap_or_else(|e| {
2071 panic!("failed to remove and recreate output directory `{compare_dir}`: {e}")
2072 });
2073
2074 let new_rustdoc = TestCx {
2076 config: &Config {
2077 rustdoc_path: Some("rustdoc".into()),
2080 rustc_path: "rustc".into(),
2082 ..self.config.clone()
2083 },
2084 ..*self
2085 };
2086
2087 let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2088 let mut rustc = new_rustdoc.make_compile_args(
2089 &new_rustdoc.testpaths.file,
2090 output_file,
2091 Emit::None,
2092 AllowUnused::Yes,
2093 LinkToAux::Yes,
2094 Vec::new(),
2095 );
2096 let aux_dir = new_rustdoc.aux_output_dir();
2097 new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);
2098
2099 let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
2100 if !proc_res.status.success() {
2101 eprintln!("failed to run nightly rustdoc");
2102 return;
2103 }
2104
2105 #[rustfmt::skip]
2106 let tidy_args = [
2107 "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar",
2108 "--indent", "yes",
2109 "--indent-spaces", "2",
2110 "--wrap", "0",
2111 "--show-warnings", "no",
2112 "--markup", "yes",
2113 "--quiet", "yes",
2114 "-modify",
2115 ];
2116 let tidy_dir = |dir| {
2117 for entry in walkdir::WalkDir::new(dir) {
2118 let entry = entry.expect("failed to read file");
2119 if entry.file_type().is_file()
2120 && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2121 {
2122 let status =
2123 Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2124 assert!(status.success() || status.code() == Some(1));
2126 }
2127 }
2128 };
2129 tidy_dir(out_dir);
2130 tidy_dir(&compare_dir);
2131
2132 let pager = {
2133 let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2134 output.and_then(|out| {
2135 if out.status.success() {
2136 Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2137 } else {
2138 None
2139 }
2140 })
2141 };
2142
2143 let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2144
2145 if !write_filtered_diff(
2146 &diff_filename,
2147 out_dir,
2148 &compare_dir,
2149 self.config.verbose,
2150 |file_type, extension| {
2151 file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2152 },
2153 ) {
2154 return;
2155 }
2156
2157 match self.config.color {
2158 ColorConfig::AlwaysColor => colored::control::set_override(true),
2159 ColorConfig::NeverColor => colored::control::set_override(false),
2160 _ => {}
2161 }
2162
2163 if let Some(pager) = pager {
2164 let pager = pager.trim();
2165 if self.config.verbose {
2166 eprintln!("using pager {}", pager);
2167 }
2168 let output = Command::new(pager)
2169 .env("PAGER", "")
2171 .stdin(File::open(&diff_filename).unwrap())
2172 .output()
2175 .unwrap();
2176 assert!(output.status.success());
2177 println!("{}", String::from_utf8_lossy(&output.stdout));
2178 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
2179 } else {
2180 warning!("no pager configured, falling back to unified diff");
2181 help!(
2182 "try configuring a git pager (e.g. `delta`) with \
2183 `git config --global core.pager delta`"
2184 );
2185 let mut out = io::stdout();
2186 let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2187 let mut line = Vec::new();
2188 loop {
2189 line.truncate(0);
2190 match diff.read_until(b'\n', &mut line) {
2191 Ok(0) => break,
2192 Ok(_) => {}
2193 Err(e) => eprintln!("ERROR: {:?}", e),
2194 }
2195 match String::from_utf8(line.clone()) {
2196 Ok(line) => {
2197 if line.starts_with('+') {
2198 write!(&mut out, "{}", line.green()).unwrap();
2199 } else if line.starts_with('-') {
2200 write!(&mut out, "{}", line.red()).unwrap();
2201 } else if line.starts_with('@') {
2202 write!(&mut out, "{}", line.blue()).unwrap();
2203 } else {
2204 out.write_all(line.as_bytes()).unwrap();
2205 }
2206 }
2207 Err(_) => {
2208 write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2209 }
2210 }
2211 }
2212 };
2213 }
2214
2215 fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
2216 let content = fs::read_to_string(path.as_std_path()).unwrap();
2217 let mut ignore = false;
2218 content
2219 .lines()
2220 .enumerate()
2221 .filter_map(|(line_nb, line)| {
2222 if (line.trim_start().starts_with("pub mod ")
2223 || line.trim_start().starts_with("mod "))
2224 && line.ends_with(';')
2225 {
2226 if let Some(ref mut other_files) = other_files {
2227 other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2228 }
2229 None
2230 } else {
2231 let sline = line.rsplit("///").next().unwrap();
2232 let line = sline.trim_start();
2233 if line.starts_with("```") {
2234 if ignore {
2235 ignore = false;
2236 None
2237 } else {
2238 ignore = true;
2239 Some(line_nb + 1)
2240 }
2241 } else {
2242 None
2243 }
2244 }
2245 })
2246 .collect()
2247 }
2248
2249 fn check_rustdoc_test_option(&self, res: ProcRes) {
2254 let mut other_files = Vec::new();
2255 let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2256 let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2257 let normalized = normalized.to_str().unwrap().replace('\\', "/");
2258 files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2259 for other_file in other_files {
2260 let mut path = self.testpaths.file.clone();
2261 path.set_file_name(&format!("{}.rs", other_file));
2262 let path = path.canonicalize_utf8().expect("failed to canonicalize");
2263 let normalized = path.as_str().replace('\\', "/");
2264 files.insert(normalized, self.get_lines(&path, None));
2265 }
2266
2267 let mut tested = 0;
2268 for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2269 if let Some((left, right)) = s.split_once(" - ") {
2270 let path = left.rsplit("test ").next().unwrap();
2271 let path = fs::canonicalize(&path).expect("failed to canonicalize");
2272 let path = path.to_str().unwrap().replace('\\', "/");
2273 if let Some(ref mut v) = files.get_mut(&path) {
2274 tested += 1;
2275 let mut iter = right.split("(line ");
2276 iter.next();
2277 let line = iter
2278 .next()
2279 .unwrap_or(")")
2280 .split(')')
2281 .next()
2282 .unwrap_or("0")
2283 .parse()
2284 .unwrap_or(0);
2285 if let Ok(pos) = v.binary_search(&line) {
2286 v.remove(pos);
2287 } else {
2288 self.fatal_proc_rec(
2289 &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2290 &res,
2291 );
2292 }
2293 }
2294 }
2295 }) {}
2296 if tested == 0 {
2297 self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2298 } else {
2299 for (entry, v) in &files {
2300 if !v.is_empty() {
2301 self.fatal_proc_rec(
2302 &format!(
2303 "Not found test at line{} \"{}\":{:?}",
2304 if v.len() > 1 { "s" } else { "" },
2305 entry,
2306 v
2307 ),
2308 &res,
2309 );
2310 }
2311 }
2312 }
2313 }
2314
2315 fn force_color_svg(&self) -> bool {
2316 self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2317 }
2318
2319 fn load_compare_outputs(
2320 &self,
2321 proc_res: &ProcRes,
2322 output_kind: TestOutput,
2323 explicit_format: bool,
2324 ) -> usize {
2325 let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2326 let (stderr_kind, stdout_kind) = match output_kind {
2327 TestOutput::Compile => (
2328 if self.force_color_svg() {
2329 if self.config.target.contains("windows") {
2330 UI_WINDOWS_SVG
2333 } else {
2334 UI_SVG
2335 }
2336 } else if self.props.stderr_per_bitwidth {
2337 &stderr_bits
2338 } else {
2339 UI_STDERR
2340 },
2341 UI_STDOUT,
2342 ),
2343 TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2344 };
2345
2346 let expected_stderr = self.load_expected_output(stderr_kind);
2347 let expected_stdout = self.load_expected_output(stdout_kind);
2348
2349 let mut normalized_stdout =
2350 self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2351 match output_kind {
2352 TestOutput::Run if self.config.remote_test_client.is_some() => {
2353 normalized_stdout = static_regex!(
2358 "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2359 )
2360 .replace(&normalized_stdout, "")
2361 .to_string();
2362 normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2365 .replace(&normalized_stdout, "")
2366 .to_string();
2367 }
2370 _ => {}
2371 };
2372
2373 let stderr = if self.force_color_svg() {
2374 anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2375 } else if explicit_format {
2376 proc_res.stderr.clone()
2377 } else {
2378 json::extract_rendered(&proc_res.stderr)
2379 };
2380
2381 let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2382 let mut errors = 0;
2383 match output_kind {
2384 TestOutput::Compile => {
2385 if !self.props.dont_check_compiler_stdout {
2386 if self
2387 .compare_output(
2388 stdout_kind,
2389 &normalized_stdout,
2390 &proc_res.stdout,
2391 &expected_stdout,
2392 )
2393 .should_error()
2394 {
2395 errors += 1;
2396 }
2397 }
2398 if !self.props.dont_check_compiler_stderr {
2399 if self
2400 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2401 .should_error()
2402 {
2403 errors += 1;
2404 }
2405 }
2406 }
2407 TestOutput::Run => {
2408 if self
2409 .compare_output(
2410 stdout_kind,
2411 &normalized_stdout,
2412 &proc_res.stdout,
2413 &expected_stdout,
2414 )
2415 .should_error()
2416 {
2417 errors += 1;
2418 }
2419
2420 if self
2421 .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2422 .should_error()
2423 {
2424 errors += 1;
2425 }
2426 }
2427 }
2428 errors
2429 }
2430
2431 fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2432 let rflags = self.props.run_flags.join(" ");
2435 let cflags = self.props.compile_flags.join(" ");
2436 let json = rflags.contains("--format json")
2437 || rflags.contains("--format=json")
2438 || cflags.contains("--error-format json")
2439 || cflags.contains("--error-format pretty-json")
2440 || cflags.contains("--error-format=json")
2441 || cflags.contains("--error-format=pretty-json")
2442 || cflags.contains("--output-format json")
2443 || cflags.contains("--output-format=json");
2444
2445 let mut normalized = output.to_string();
2446
2447 let mut normalize_path = |from: &Utf8Path, to: &str| {
2448 let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
2449
2450 normalized = normalized.replace(from, to);
2451 };
2452
2453 let parent_dir = self.testpaths.file.parent().unwrap();
2454 normalize_path(parent_dir, "$DIR");
2455
2456 if self.props.remap_src_base {
2457 let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
2458 if self.testpaths.relative_dir != Utf8Path::new("") {
2459 remapped_parent_dir.push(&self.testpaths.relative_dir);
2460 }
2461 normalize_path(&remapped_parent_dir, "$DIR");
2462 }
2463
2464 let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
2465 normalize_path(&base_dir.join("library"), "$SRC_DIR");
2467 normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2471
2472 let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2474 rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
2475 let rust_src_dir =
2476 rust_src_dir.read_link_utf8().unwrap_or_else(|_| rust_src_dir.to_path_buf());
2477 normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2478
2479 let rustc_src_dir = &self.config.sysroot_base.join("lib/rustlib/rustc-src/rust");
2481 rustc_src_dir.try_exists().expect(&*format!("{} should exists", rustc_src_dir));
2482 let rustc_src_dir = rustc_src_dir.read_link_utf8().unwrap_or(rustc_src_dir.to_path_buf());
2483 normalize_path(&rustc_src_dir.join("compiler"), "$COMPILER_DIR_REAL");
2484
2485 normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2488 normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
2495 normalize_path(&self.config.build_root, "$BUILD_DIR");
2497
2498 if json {
2499 normalized = normalized.replace("\\n", "\n");
2504 }
2505
2506 normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2511 .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2512 .into_owned();
2513
2514 normalized = Self::normalize_platform_differences(&normalized);
2515
2516 normalized =
2518 static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2519 .replace_all(&normalized, |caps: &Captures<'_>| {
2520 format!(
2521 "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2522 filename = &caps["filename"]
2523 )
2524 })
2525 .into_owned();
2526
2527 normalized = normalized.replace("\t", "\\t"); normalized =
2534 static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2535
2536 let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2539 let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2540
2541 const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2542 if v0_crate_hash_prefix_re.is_match(&normalized) {
2543 normalized =
2545 v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2546 }
2547
2548 let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2549 let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2550
2551 const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2552 if v0_back_ref_prefix_re.is_match(&normalized) {
2553 normalized =
2555 v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2556 }
2557
2558 {
2565 let mut seen_allocs = indexmap::IndexSet::new();
2566
2567 normalized = static_regex!(
2569 r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2570 )
2571 .replace_all(&normalized, |caps: &Captures<'_>| {
2572 let index = caps.get(2).unwrap().as_str().to_string();
2574 let (index, _) = seen_allocs.insert_full(index);
2575 let offset = caps.get(3).map_or("", |c| c.as_str());
2576 let imm = caps.get(4).map_or("", |c| c.as_str());
2577 format!("╾ALLOC{index}{offset}{imm}╼")
2579 })
2580 .into_owned();
2581
2582 normalized = static_regex!(r"\balloc([0-9]+)\b")
2584 .replace_all(&normalized, |caps: &Captures<'_>| {
2585 let index = caps.get(1).unwrap().as_str().to_string();
2586 let (index, _) = seen_allocs.insert_full(index);
2587 format!("ALLOC{index}")
2588 })
2589 .into_owned();
2590 }
2591
2592 for rule in custom_rules {
2594 let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2595 normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2596 }
2597 normalized
2598 }
2599
2600 fn normalize_platform_differences(output: &str) -> String {
2606 let output = output.replace(r"\\", r"\");
2607
2608 static_regex!(
2613 r#"(?x)
2614 (?:
2615 # Match paths that don't include spaces.
2616 (?:\\[\pL\pN\.\-_']+)+\.\pL+
2617 |
2618 # If the path starts with a well-known root, then allow spaces and no file extension.
2619 \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2620 )"#
2621 )
2622 .replace_all(&output, |caps: &Captures<'_>| {
2623 println!("{}", &caps[0]);
2624 caps[0].replace(r"\", "/")
2625 })
2626 .replace("\r\n", "\n")
2627 }
2628
2629 fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
2630 let mut path =
2631 expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2632
2633 if !path.exists() {
2634 if let Some(CompareMode::Polonius) = self.config.compare_mode {
2635 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2636 }
2637 }
2638
2639 if !path.exists() {
2640 path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2641 }
2642
2643 path
2644 }
2645
2646 fn load_expected_output(&self, kind: &str) -> String {
2647 let path = self.expected_output_path(kind);
2648 if path.exists() {
2649 match self.load_expected_output_from_path(&path) {
2650 Ok(x) => x,
2651 Err(x) => self.fatal(&x),
2652 }
2653 } else {
2654 String::new()
2655 }
2656 }
2657
2658 fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
2659 fs::read_to_string(path)
2660 .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
2661 }
2662
2663 fn delete_file(&self, file: &Utf8Path) {
2664 if !file.exists() {
2665 return;
2667 }
2668 if let Err(e) = fs::remove_file(file.as_std_path()) {
2669 self.fatal(&format!("failed to delete `{}`: {}", file, e,));
2670 }
2671 }
2672
2673 fn compare_output(
2674 &self,
2675 stream: &str,
2676 actual: &str,
2677 actual_unnormalized: &str,
2678 expected: &str,
2679 ) -> CompareOutcome {
2680 let expected_path =
2681 expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
2682
2683 if self.config.bless && actual.is_empty() && expected_path.exists() {
2684 self.delete_file(&expected_path);
2685 }
2686
2687 let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2688 (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2691 _ => expected != actual,
2692 };
2693 if !are_different {
2694 return CompareOutcome::Same;
2695 }
2696
2697 let compare_output_by_lines = self.config.runner.is_some();
2701
2702 let tmp;
2703 let (expected, actual): (&str, &str) = if compare_output_by_lines {
2704 let actual_lines: HashSet<_> = actual.lines().collect();
2705 let expected_lines: Vec<_> = expected.lines().collect();
2706 let mut used = expected_lines.clone();
2707 used.retain(|line| actual_lines.contains(line));
2708 if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2710 return CompareOutcome::Same;
2711 }
2712 if expected_lines.is_empty() {
2713 ("", actual)
2715 } else {
2716 tmp = (expected_lines.join("\n"), used.join("\n"));
2717 (&tmp.0, &tmp.1)
2718 }
2719 } else {
2720 (expected, actual)
2721 };
2722
2723 let actual_path = self
2725 .output_base_name()
2726 .with_extra_extension(self.revision.unwrap_or(""))
2727 .with_extra_extension(
2728 self.config.compare_mode.as_ref().map(|cm| cm.to_str()).unwrap_or(""),
2729 )
2730 .with_extra_extension(stream);
2731
2732 if let Err(err) = fs::write(&actual_path, &actual) {
2733 self.fatal(&format!("failed to write {stream} to `{actual_path}`: {err}",));
2734 }
2735 println!("Saved the actual {stream} to `{actual_path}`");
2736
2737 if !self.config.bless {
2738 if expected.is_empty() {
2739 println!("normalized {}:\n{}\n", stream, actual);
2740 } else {
2741 self.show_diff(
2742 stream,
2743 &expected_path,
2744 &actual_path,
2745 expected,
2746 actual,
2747 actual_unnormalized,
2748 );
2749 }
2750 } else {
2751 if self.revision.is_some() {
2754 let old =
2755 expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2756 self.delete_file(&old);
2757 }
2758
2759 if !actual.is_empty() {
2760 if let Err(err) = fs::write(&expected_path, &actual) {
2761 self.fatal(&format!("failed to write {stream} to `{expected_path}`: {err}"));
2762 }
2763 println!(
2764 "Blessing the {stream} of `{test_name}` as `{expected_path}`",
2765 test_name = self.testpaths.file
2766 );
2767 }
2768 }
2769
2770 println!("\nThe actual {stream} differed from the expected {stream}");
2771
2772 if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2773 }
2774
2775 fn show_diff(
2777 &self,
2778 stream: &str,
2779 expected_path: &Utf8Path,
2780 actual_path: &Utf8Path,
2781 expected: &str,
2782 actual: &str,
2783 actual_unnormalized: &str,
2784 ) {
2785 eprintln!("diff of {stream}:\n");
2786 if let Some(diff_command) = self.config.diff_command.as_deref() {
2787 let mut args = diff_command.split_whitespace();
2788 let name = args.next().unwrap();
2789 match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2790 Err(err) => {
2791 self.fatal(&format!(
2792 "failed to call custom diff command `{diff_command}`: {err}"
2793 ));
2794 }
2795 Ok(output) => {
2796 let output = String::from_utf8_lossy(&output.stdout);
2797 eprint!("{output}");
2798 }
2799 }
2800 } else {
2801 eprint!("{}", write_diff(expected, actual, 3));
2802 }
2803
2804 let diff_results = make_diff(actual, expected, 0);
2806
2807 let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2808 for hunk in diff_results {
2809 let mut line_no = hunk.line_number;
2810 for line in hunk.lines {
2811 if let DiffLine::Expected(normalized) = line {
2813 mismatches_normalized += &normalized;
2814 mismatches_normalized += "\n";
2815 mismatch_line_nos.push(line_no);
2816 line_no += 1;
2817 }
2818 }
2819 }
2820 let mut mismatches_unnormalized = String::new();
2821 let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2822 for hunk in diff_normalized {
2823 if mismatch_line_nos.contains(&hunk.line_number) {
2824 for line in hunk.lines {
2825 if let DiffLine::Resulting(unnormalized) = line {
2826 mismatches_unnormalized += &unnormalized;
2827 mismatches_unnormalized += "\n";
2828 }
2829 }
2830 }
2831 }
2832
2833 let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2834 if !normalized_diff.is_empty()
2836 && !mismatches_unnormalized.is_empty()
2837 && !mismatches_normalized.is_empty()
2838 {
2839 eprintln!("Note: some mismatched output was normalized before being compared");
2840 eprint!("{}", write_diff(&mismatches_unnormalized, &mismatches_normalized, 0));
2842 }
2843 }
2844
2845 fn check_and_prune_duplicate_outputs(
2846 &self,
2847 proc_res: &ProcRes,
2848 modes: &[CompareMode],
2849 require_same_modes: &[CompareMode],
2850 ) {
2851 for kind in UI_EXTENSIONS {
2852 let canon_comparison_path =
2853 expected_output_path(&self.testpaths, self.revision, &None, kind);
2854
2855 let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2856 Ok(canon) => canon,
2857 _ => continue,
2858 };
2859 let bless = self.config.bless;
2860 let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2861 let examined_path =
2862 expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind);
2863
2864 let examined_content = match self.load_expected_output_from_path(&examined_path) {
2866 Ok(content) => content,
2867 _ => return,
2868 };
2869
2870 let is_duplicate = canon == examined_content;
2871
2872 match (bless, require_same, is_duplicate) {
2873 (true, _, true) => {
2875 self.delete_file(&examined_path);
2876 }
2877 (_, true, false) => {
2880 self.fatal_proc_rec(
2881 &format!("`{}` should not have different output from base test!", kind),
2882 proc_res,
2883 );
2884 }
2885 _ => {}
2886 }
2887 };
2888 for mode in modes {
2889 check_and_prune_duplicate_outputs(mode, false);
2890 }
2891 for mode in require_same_modes {
2892 check_and_prune_duplicate_outputs(mode, true);
2893 }
2894 }
2895 }
2896
2897 fn create_stamp(&self) {
2898 let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision);
2899 fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap();
2900 }
2901
2902 fn init_incremental_test(&self) {
2903 let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
2910 if incremental_dir.exists() {
2911 let canonicalized = incremental_dir.canonicalize().unwrap();
2914 fs::remove_dir_all(canonicalized).unwrap();
2915 }
2916 fs::create_dir_all(&incremental_dir).unwrap();
2917
2918 if self.config.verbose {
2919 println!("init_incremental_test: incremental_dir={incremental_dir}");
2920 }
2921 }
2922}
2923
2924struct ProcArgs {
2925 prog: OsString,
2926 args: Vec<OsString>,
2927}
2928
2929pub struct ProcRes {
2930 status: ExitStatus,
2931 stdout: String,
2932 stderr: String,
2933 truncated: Truncated,
2934 cmdline: String,
2935}
2936
2937impl ProcRes {
2938 pub fn print_info(&self) {
2939 fn render(name: &str, contents: &str) -> String {
2940 let contents = json::extract_rendered(contents);
2941 let contents = contents.trim_end();
2942 if contents.is_empty() {
2943 format!("{name}: none")
2944 } else {
2945 format!(
2946 "\
2947 --- {name} -------------------------------\n\
2948 {contents}\n\
2949 ------------------------------------------",
2950 )
2951 }
2952 }
2953
2954 println!(
2955 "status: {}\ncommand: {}\n{}\n{}\n",
2956 self.status,
2957 self.cmdline,
2958 render("stdout", &self.stdout),
2959 render("stderr", &self.stderr),
2960 );
2961 }
2962
2963 pub fn fatal(&self, err: Option<&str>, on_failure: impl FnOnce()) -> ! {
2964 if let Some(e) = err {
2965 println!("\nerror: {}", e);
2966 }
2967 self.print_info();
2968 on_failure();
2969 std::panic::resume_unwind(Box::new(()));
2972 }
2973}
2974
2975#[derive(Debug)]
2976enum TargetLocation {
2977 ThisFile(Utf8PathBuf),
2978 ThisDirectory(Utf8PathBuf),
2979}
2980
2981enum AllowUnused {
2982 Yes,
2983 No,
2984}
2985
2986enum LinkToAux {
2987 Yes,
2988 No,
2989}
2990
2991#[derive(Debug, PartialEq)]
2992enum AuxType {
2993 Bin,
2994 Lib,
2995 Dylib,
2996 ProcMacro,
2997}
2998
2999#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3002enum CompareOutcome {
3003 Same,
3005 Blessed,
3007 Differed,
3009}
3010
3011impl CompareOutcome {
3012 fn should_error(&self) -> bool {
3013 matches!(self, CompareOutcome::Differed)
3014 }
3015}