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