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