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