compiletest/
runtest.rs

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
30// Helper modules that implement test running logic for each test suite.
31// tidy-alphabetical-start
32mod 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;
46// tidy-alphabetical-end
47
48mod 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    // Error mode is a global variable, so lock it so only one thread will change it
66    let _lock = LOCK.lock().unwrap();
67
68    // Tell Windows to not show any UI on errors (such as terminating abnormally). This is important
69    // for running tests, since some of them use abnormal termination by design. This mode is
70    // inherited by all child processes.
71    //
72    // Note that `run-make` tests require `SEM_FAILCRITICALERRORS` in addition to suppress Windows
73    // Error Reporting (WER) error dialogues that come from "critical failures" such as missing
74    // DLLs.
75    //
76    // See <https://github.com/rust-lang/rust/issues/132092> and
77    // <https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode?redirectedfrom=MSDN>.
78    unsafe {
79        // read inherited flags
80        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
93/// The platform-specific library name
94fn get_lib_name(name: &str, aux_type: AuxType) -> Option<String> {
95    match aux_type {
96        AuxType::Bin => None,
97        // In some cases (e.g. MUSL), we build a static
98        // library, rather than a dynamic library.
99        // In this case, the only path we can pass
100        // with '--extern-meta' is the '.rlib' file
101        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            // FIXME: this logic seems strange as well.
129
130            // android has its own gdb handling
131            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        // We're going to be dumping a lot of info. Start on a new line.
139        write!(stdout, "\n\n");
140    }
141    debug!("running {}", testpaths.file);
142    let mut props = TestProps::from_file(&testpaths.file, revision, &config);
143
144    // For non-incremental (i.e. regular UI) tests, the incremental directory
145    // takes into account the revision name, since the revisions are independent
146    // of each other and can race.
147    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        // Incremental tests are special because they cannot be run in
163        // parallel.
164        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.python.hash(&mut hash);
204            config.lldb_python_dir.hash(&mut hash);
205            env::var_os("PATH").hash(&mut hash);
206            env::var_os("PYTHONPATH").hash(&mut hash);
207        }
208
209        None => {}
210    }
211
212    if config.mode == TestMode::Ui {
213        config.force_pass_mode.hash(&mut hash);
214    }
215
216    format!("{:x}", hash.finish())
217}
218
219#[derive(Copy, Clone, Debug)]
220struct TestCx<'test> {
221    config: &'test Config,
222    stdout: &'test dyn ConsoleOut,
223    stderr: &'test dyn ConsoleOut,
224    props: &'test TestProps,
225    testpaths: &'test TestPaths,
226    revision: Option<&'test str>,
227}
228
229enum ReadFrom {
230    Path,
231    Stdin(String),
232}
233
234enum TestOutput {
235    Compile,
236    Run,
237}
238
239/// Will this test be executed? Should we use `make_exe_name`?
240#[derive(Copy, Clone, PartialEq)]
241enum WillExecute {
242    Yes,
243    No,
244    Disabled,
245}
246
247/// What value should be passed to `--emit`?
248#[derive(Copy, Clone)]
249enum Emit {
250    None,
251    Metadata,
252    LlvmIr,
253    Mir,
254    Asm,
255    LinkArgsAsm,
256}
257
258impl<'test> TestCx<'test> {
259    /// Code executed for each revision in turn (or, if there are no
260    /// revisions, exactly once, with revision == None).
261    fn run_revision(&self) {
262        if self.props.should_ice
263            && self.config.mode != TestMode::Incremental
264            && self.config.mode != TestMode::Crashes
265        {
266            self.fatal("cannot use should-ice in a test that is not cfail");
267        }
268        match self.config.mode {
269            TestMode::Pretty => self.run_pretty_test(),
270            TestMode::DebugInfo => self.run_debuginfo_test(),
271            TestMode::Codegen => self.run_codegen_test(),
272            TestMode::Rustdoc => self.run_rustdoc_test(),
273            TestMode::RustdocJson => self.run_rustdoc_json_test(),
274            TestMode::CodegenUnits => self.run_codegen_units_test(),
275            TestMode::Incremental => self.run_incremental_test(),
276            TestMode::RunMake => self.run_rmake_test(),
277            TestMode::Ui => self.run_ui_test(),
278            TestMode::MirOpt => self.run_mir_opt_test(),
279            TestMode::Assembly => self.run_assembly_test(),
280            TestMode::RustdocJs => self.run_rustdoc_js_test(),
281            TestMode::CoverageMap => self.run_coverage_map_test(), // see self::coverage
282            TestMode::CoverageRun => self.run_coverage_run_test(), // see self::coverage
283            TestMode::Crashes => self.run_crash_test(),
284        }
285    }
286
287    fn pass_mode(&self) -> Option<PassMode> {
288        self.props.pass_mode(self.config)
289    }
290
291    fn should_run(&self, pm: Option<PassMode>) -> WillExecute {
292        let test_should_run = match self.config.mode {
293            TestMode::Ui
294                if pm == Some(PassMode::Run)
295                    || matches!(self.props.fail_mode, Some(FailMode::Run(_))) =>
296            {
297                true
298            }
299            TestMode::MirOpt if pm == Some(PassMode::Run) => true,
300            TestMode::Ui | TestMode::MirOpt => false,
301            mode => panic!("unimplemented for mode {:?}", mode),
302        };
303        if test_should_run { self.run_if_enabled() } else { WillExecute::No }
304    }
305
306    fn run_if_enabled(&self) -> WillExecute {
307        if self.config.run_enabled() { WillExecute::Yes } else { WillExecute::Disabled }
308    }
309
310    fn should_run_successfully(&self, pm: Option<PassMode>) -> bool {
311        match self.config.mode {
312            TestMode::Ui | TestMode::MirOpt => pm == Some(PassMode::Run),
313            mode => panic!("unimplemented for mode {:?}", mode),
314        }
315    }
316
317    fn should_compile_successfully(&self, pm: Option<PassMode>) -> bool {
318        match self.config.mode {
319            TestMode::RustdocJs => true,
320            TestMode::Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build),
321            TestMode::Crashes => false,
322            TestMode::Incremental => {
323                let revision =
324                    self.revision.expect("incremental tests require a list of revisions");
325                if revision.starts_with("cpass")
326                    || revision.starts_with("rpass")
327                    || revision.starts_with("rfail")
328                {
329                    true
330                } else if revision.starts_with("cfail") {
331                    pm.is_some()
332                } else {
333                    panic!("revision name must begin with cpass, rpass, rfail, or cfail");
334                }
335            }
336            mode => panic!("unimplemented for mode {:?}", mode),
337        }
338    }
339
340    fn check_if_test_should_compile(
341        &self,
342        fail_mode: Option<FailMode>,
343        pass_mode: Option<PassMode>,
344        proc_res: &ProcRes,
345    ) {
346        if self.should_compile_successfully(pass_mode) {
347            if !proc_res.status.success() {
348                match (fail_mode, pass_mode) {
349                    (Some(FailMode::Build), Some(PassMode::Check)) => {
350                        // A `build-fail` test needs to `check-pass`.
351                        self.fatal_proc_rec(
352                            "`build-fail` test is required to pass check build, but check build failed",
353                            proc_res,
354                        );
355                    }
356                    _ => {
357                        self.fatal_proc_rec(
358                            "test compilation failed although it shouldn't!",
359                            proc_res,
360                        );
361                    }
362                }
363            }
364        } else {
365            if proc_res.status.success() {
366                let err = &format!("{} test did not emit an error", self.config.mode);
367                let extra_note = (self.config.mode == crate::common::TestMode::Ui)
368                    .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.");
369                self.fatal_proc_rec_general(err, extra_note, proc_res, || ());
370            }
371
372            if !self.props.dont_check_failure_status {
373                self.check_correct_failure_status(proc_res);
374            }
375        }
376    }
377
378    fn get_output(&self, proc_res: &ProcRes) -> String {
379        if self.props.check_stdout {
380            format!("{}{}", proc_res.stdout, proc_res.stderr)
381        } else {
382            proc_res.stderr.clone()
383        }
384    }
385
386    fn check_correct_failure_status(&self, proc_res: &ProcRes) {
387        let expected_status = Some(self.props.failure_status.unwrap_or(1));
388        let received_status = proc_res.status.code();
389
390        if expected_status != received_status {
391            self.fatal_proc_rec(
392                &format!(
393                    "Error: expected failure status ({:?}) but received status {:?}.",
394                    expected_status, received_status
395                ),
396                proc_res,
397            );
398        }
399    }
400
401    /// Runs a [`Command`] and waits for it to finish, then converts its exit
402    /// status and output streams into a [`ProcRes`].
403    ///
404    /// The command might have succeeded or failed; it is the caller's
405    /// responsibility to check the exit status and take appropriate action.
406    ///
407    /// # Panics
408    /// Panics if the command couldn't be executed at all
409    /// (e.g. because the executable could not be found).
410    #[must_use = "caller should check whether the command succeeded"]
411    fn run_command_to_procres(&self, cmd: &mut Command) -> ProcRes {
412        let output = cmd
413            .output()
414            .unwrap_or_else(|e| self.fatal(&format!("failed to exec `{cmd:?}` because: {e}")));
415
416        let proc_res = ProcRes {
417            status: output.status,
418            stdout: String::from_utf8(output.stdout).unwrap(),
419            stderr: String::from_utf8(output.stderr).unwrap(),
420            truncated: Truncated::No,
421            cmdline: format!("{cmd:?}"),
422        };
423        self.dump_output(
424            self.config.verbose || !proc_res.status.success(),
425            &cmd.get_program().to_string_lossy(),
426            &proc_res.stdout,
427            &proc_res.stderr,
428        );
429
430        proc_res
431    }
432
433    fn print_source(&self, read_from: ReadFrom, pretty_type: &str) -> ProcRes {
434        let aux_dir = self.aux_output_dir_name();
435        let input: &str = match read_from {
436            ReadFrom::Stdin(_) => "-",
437            ReadFrom::Path => self.testpaths.file.as_str(),
438        };
439
440        let mut rustc = Command::new(&self.config.rustc_path);
441        rustc
442            .arg(input)
443            .args(&["-Z", &format!("unpretty={}", pretty_type)])
444            .args(&["--target", &self.config.target])
445            .arg("-L")
446            .arg(&aux_dir)
447            .arg("-A")
448            .arg("internal_features")
449            .args(&self.props.compile_flags)
450            .envs(self.props.rustc_env.clone());
451        self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
452
453        let src = match read_from {
454            ReadFrom::Stdin(src) => Some(src),
455            ReadFrom::Path => None,
456        };
457
458        self.compose_and_run(
459            rustc,
460            self.config.compile_lib_path.as_path(),
461            Some(aux_dir.as_path()),
462            src,
463        )
464    }
465
466    fn compare_source(&self, expected: &str, actual: &str) {
467        if expected != actual {
468            self.fatal(&format!(
469                "pretty-printed source does not match expected source\n\
470                 expected:\n\
471                 ------------------------------------------\n\
472                 {}\n\
473                 ------------------------------------------\n\
474                 actual:\n\
475                 ------------------------------------------\n\
476                 {}\n\
477                 ------------------------------------------\n\
478                 diff:\n\
479                 ------------------------------------------\n\
480                 {}\n",
481                expected,
482                actual,
483                write_diff(expected, actual, 3),
484            ));
485        }
486    }
487
488    fn set_revision_flags(&self, cmd: &mut Command) {
489        // Normalize revisions to be lowercase and replace `-`s with `_`s.
490        // Otherwise the `--cfg` flag is not valid.
491        let normalize_revision = |revision: &str| revision.to_lowercase().replace("-", "_");
492
493        if let Some(revision) = self.revision {
494            let normalized_revision = normalize_revision(revision);
495            let cfg_arg = ["--cfg", &normalized_revision];
496            let arg = format!("--cfg={normalized_revision}");
497            if self
498                .props
499                .compile_flags
500                .windows(2)
501                .any(|args| args == cfg_arg || args[0] == arg || args[1] == arg)
502            {
503                error!(
504                    "redundant cfg argument `{normalized_revision}` is already created by the \
505                    revision"
506                );
507                panic!("redundant cfg argument");
508            }
509            if self.config.builtin_cfg_names().contains(&normalized_revision) {
510                error!("revision `{normalized_revision}` collides with a built-in cfg");
511                panic!("revision collides with built-in cfg");
512            }
513            cmd.args(cfg_arg);
514        }
515
516        if !self.props.no_auto_check_cfg {
517            let mut check_cfg = String::with_capacity(25);
518
519            // Generate `cfg(FALSE, REV1, ..., REVN)` (for all possible revisions)
520            //
521            // For compatibility reason we consider the `FALSE` cfg to be expected
522            // since it is extensively used in the testsuite, as well as the `test`
523            // cfg since we have tests that uses it.
524            check_cfg.push_str("cfg(test,FALSE");
525            for revision in &self.props.revisions {
526                check_cfg.push(',');
527                check_cfg.push_str(&normalize_revision(revision));
528            }
529            check_cfg.push(')');
530
531            cmd.args(&["--check-cfg", &check_cfg]);
532        }
533    }
534
535    fn typecheck_source(&self, src: String) -> ProcRes {
536        let mut rustc = Command::new(&self.config.rustc_path);
537
538        let out_dir = self.output_base_name().with_extension("pretty-out");
539        remove_and_create_dir_all(&out_dir).unwrap_or_else(|e| {
540            panic!("failed to remove and recreate output directory `{out_dir}`: {e}")
541        });
542
543        let target = if self.props.force_host { &*self.config.host } else { &*self.config.target };
544
545        let aux_dir = self.aux_output_dir_name();
546
547        rustc
548            .arg("-")
549            .arg("-Zno-codegen")
550            .arg("--out-dir")
551            .arg(&out_dir)
552            .arg(&format!("--target={}", target))
553            .arg("-L")
554            // FIXME(jieyouxu): this search path seems questionable. Is this intended for
555            // `rust_test_helpers` in ui tests?
556            .arg(&self.config.build_test_suite_root)
557            .arg("-L")
558            .arg(aux_dir)
559            .arg("-A")
560            .arg("internal_features");
561        self.set_revision_flags(&mut rustc);
562        self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
563        rustc.args(&self.props.compile_flags);
564
565        self.compose_and_run_compiler(rustc, Some(src))
566    }
567
568    fn maybe_add_external_args(&self, cmd: &mut Command, args: &Vec<String>) {
569        // Filter out the arguments that should not be added by runtest here.
570        //
571        // Notable use-cases are: do not add our optimisation flag if
572        // `compile-flags: -Copt-level=x` and similar for debug-info level as well.
573        const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", /*-C<space>*/ "opt-level="];
574        const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", /*-C<space>*/ "debuginfo="];
575
576        // FIXME: ideally we would "just" check the `cmd` itself, but it does not allow inspecting
577        // its arguments. They need to be collected separately. For now I cannot be bothered to
578        // implement this the "right" way.
579        let have_opt_flag =
580            self.props.compile_flags.iter().any(|arg| OPT_FLAGS.iter().any(|f| arg.starts_with(f)));
581        let have_debug_flag = self
582            .props
583            .compile_flags
584            .iter()
585            .any(|arg| DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)));
586
587        for arg in args {
588            if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag {
589                continue;
590            }
591            if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag {
592                continue;
593            }
594            cmd.arg(arg);
595        }
596    }
597
598    /// Check `error-pattern` and `regex-error-pattern` directives.
599    fn check_all_error_patterns(&self, output_to_check: &str, proc_res: &ProcRes) {
600        let mut missing_patterns: Vec<String> = Vec::new();
601        self.check_error_patterns(output_to_check, &mut missing_patterns);
602        self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns);
603
604        if missing_patterns.is_empty() {
605            return;
606        }
607
608        if missing_patterns.len() == 1 {
609            self.fatal_proc_rec(
610                &format!("error pattern '{}' not found!", missing_patterns[0]),
611                proc_res,
612            );
613        } else {
614            for pattern in missing_patterns {
615                writeln!(
616                    self.stdout,
617                    "\n{prefix}: error pattern '{pattern}' not found!",
618                    prefix = self.error_prefix()
619                );
620            }
621            self.fatal_proc_rec("multiple error patterns not found", proc_res);
622        }
623    }
624
625    fn check_error_patterns(&self, output_to_check: &str, missing_patterns: &mut Vec<String>) {
626        debug!("check_error_patterns");
627        for pattern in &self.props.error_patterns {
628            if output_to_check.contains(pattern.trim()) {
629                debug!("found error pattern {}", pattern);
630            } else {
631                missing_patterns.push(pattern.to_string());
632            }
633        }
634    }
635
636    fn check_regex_error_patterns(
637        &self,
638        output_to_check: &str,
639        proc_res: &ProcRes,
640        missing_patterns: &mut Vec<String>,
641    ) {
642        debug!("check_regex_error_patterns");
643
644        for pattern in &self.props.regex_error_patterns {
645            let pattern = pattern.trim();
646            let re = match Regex::new(pattern) {
647                Ok(re) => re,
648                Err(err) => {
649                    self.fatal_proc_rec(
650                        &format!("invalid regex error pattern '{}': {:?}", pattern, err),
651                        proc_res,
652                    );
653                }
654            };
655            if re.is_match(output_to_check) {
656                debug!("found regex error pattern {}", pattern);
657            } else {
658                missing_patterns.push(pattern.to_string());
659            }
660        }
661    }
662
663    fn check_no_compiler_crash(&self, proc_res: &ProcRes, should_ice: bool) {
664        match proc_res.status.code() {
665            Some(101) if !should_ice => {
666                self.fatal_proc_rec("compiler encountered internal error", proc_res)
667            }
668            None => self.fatal_proc_rec("compiler terminated by signal", proc_res),
669            _ => (),
670        }
671    }
672
673    fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) {
674        for pat in &self.props.forbid_output {
675            if output_to_check.contains(pat) {
676                self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res);
677            }
678        }
679    }
680
681    /// Check `//~ KIND message` annotations.
682    fn check_expected_errors(&self, proc_res: &ProcRes) {
683        let expected_errors = load_errors(&self.testpaths.file, self.revision);
684        debug!(
685            "check_expected_errors: expected_errors={:?} proc_res.status={:?}",
686            expected_errors, proc_res.status
687        );
688        if proc_res.status.success() && expected_errors.iter().any(|x| x.kind == ErrorKind::Error) {
689            self.fatal_proc_rec("process did not return an error status", proc_res);
690        }
691
692        if self.props.known_bug {
693            if !expected_errors.is_empty() {
694                self.fatal_proc_rec(
695                    "`known_bug` tests should not have an expected error",
696                    proc_res,
697                );
698            }
699            return;
700        }
701
702        // On Windows, keep all '\' path separators to match the paths reported in the JSON output
703        // from the compiler
704        let diagnostic_file_name = if self.props.remap_src_base {
705            let mut p = Utf8PathBuf::from(FAKE_SRC_BASE);
706            p.push(&self.testpaths.relative_dir);
707            p.push(self.testpaths.file.file_name().unwrap());
708            p.to_string()
709        } else {
710            self.testpaths.file.to_string()
711        };
712
713        // Errors and warnings are always expected, other diagnostics are only expected
714        // if one of them actually occurs in the test.
715        let expected_kinds: HashSet<_> = [ErrorKind::Error, ErrorKind::Warning]
716            .into_iter()
717            .chain(expected_errors.iter().map(|e| e.kind))
718            .collect();
719
720        // Parse the JSON output from the compiler and extract out the messages.
721        let actual_errors = json::parse_output(&diagnostic_file_name, &self.get_output(proc_res))
722            .into_iter()
723            .map(|e| Error { msg: self.normalize_output(&e.msg, &[]), ..e });
724
725        let mut unexpected = Vec::new();
726        let mut unimportant = Vec::new();
727        let mut found = vec![false; expected_errors.len()];
728        for actual_error in actual_errors {
729            for pattern in &self.props.error_patterns {
730                let pattern = pattern.trim();
731                if actual_error.msg.contains(pattern) {
732                    let q = if actual_error.line_num.is_none() { "?" } else { "" };
733                    self.fatal(&format!(
734                        "error pattern '{pattern}' is found in structured \
735                         diagnostics, use `//~{q} {} {pattern}` instead",
736                        actual_error.kind,
737                    ));
738                }
739            }
740
741            let opt_index =
742                expected_errors.iter().enumerate().position(|(index, expected_error)| {
743                    !found[index]
744                        && actual_error.line_num == expected_error.line_num
745                        && actual_error.kind == expected_error.kind
746                        && actual_error.msg.contains(&expected_error.msg)
747                });
748
749            match opt_index {
750                Some(index) => {
751                    // found a match, everybody is happy
752                    assert!(!found[index]);
753                    found[index] = true;
754                }
755
756                None => {
757                    if actual_error.require_annotation
758                        && expected_kinds.contains(&actual_error.kind)
759                        && !self.props.dont_require_annotations.contains(&actual_error.kind)
760                    {
761                        unexpected.push(actual_error);
762                    } else {
763                        unimportant.push(actual_error);
764                    }
765                }
766            }
767        }
768
769        let mut not_found = Vec::new();
770        // anything not yet found is a problem
771        for (index, expected_error) in expected_errors.iter().enumerate() {
772            if !found[index] {
773                not_found.push(expected_error);
774            }
775        }
776
777        if !unexpected.is_empty() || !not_found.is_empty() {
778            // Emit locations in a format that is short (relative paths) but "clickable" in editors.
779            // Also normalize path separators to `/`.
780            let file_name = self
781                .testpaths
782                .file
783                .strip_prefix(self.config.src_root.as_str())
784                .unwrap_or(&self.testpaths.file)
785                .to_string()
786                .replace(r"\", "/");
787            let line_str = |e: &Error| {
788                let line_num = e.line_num.map_or("?".to_string(), |line_num| line_num.to_string());
789                // `file:?:NUM` may be confusing to editors and unclickable.
790                let opt_col_num = match e.column_num {
791                    Some(col_num) if line_num != "?" => format!(":{col_num}"),
792                    _ => "".to_string(),
793                };
794                format!("{file_name}:{line_num}{opt_col_num}")
795            };
796            let print_error =
797                |e| writeln!(self.stdout, "{}: {}: {}", line_str(e), e.kind, e.msg.cyan());
798            let push_suggestion =
799                |suggestions: &mut Vec<_>, e: &Error, kind, line, msg, color, rank| {
800                    let mut ret = String::new();
801                    if kind {
802                        ret += &format!("{} {}", "with different kind:".color(color), e.kind);
803                    }
804                    if line {
805                        if !ret.is_empty() {
806                            ret.push(' ');
807                        }
808                        ret += &format!("{} {}", "on different line:".color(color), line_str(e));
809                    }
810                    if msg {
811                        if !ret.is_empty() {
812                            ret.push(' ');
813                        }
814                        ret +=
815                            &format!("{} {}", "with different message:".color(color), e.msg.cyan());
816                    }
817                    suggestions.push((ret, rank));
818                };
819            let show_suggestions = |mut suggestions: Vec<_>, prefix: &str, color| {
820                // Only show suggestions with the highest rank.
821                suggestions.sort_by_key(|(_, rank)| *rank);
822                if let Some(&(_, top_rank)) = suggestions.first() {
823                    for (suggestion, rank) in suggestions {
824                        if rank == top_rank {
825                            writeln!(self.stdout, "  {} {suggestion}", prefix.color(color));
826                        }
827                    }
828                }
829            };
830
831            // Fuzzy matching quality:
832            // - message and line / message and kind - great, suggested
833            // - only message - good, suggested
834            // - known line and kind - ok, suggested
835            // - only known line - meh, but suggested
836            // - others are not worth suggesting
837            if !unexpected.is_empty() {
838                writeln!(
839                    self.stdout,
840                    "\n{prefix}: {n} diagnostics reported in JSON output but not expected in test file",
841                    prefix = self.error_prefix(),
842                    n = unexpected.len(),
843                );
844                for error in &unexpected {
845                    print_error(error);
846                    let mut suggestions = Vec::new();
847                    for candidate in &not_found {
848                        let kind_mismatch = candidate.kind != error.kind;
849                        let mut push_red_suggestion = |line, msg, rank| {
850                            push_suggestion(
851                                &mut suggestions,
852                                candidate,
853                                kind_mismatch,
854                                line,
855                                msg,
856                                Color::Red,
857                                rank,
858                            )
859                        };
860                        if error.msg.contains(&candidate.msg) {
861                            push_red_suggestion(candidate.line_num != error.line_num, false, 0);
862                        } else if candidate.line_num.is_some()
863                            && candidate.line_num == error.line_num
864                        {
865                            push_red_suggestion(false, true, if kind_mismatch { 2 } else { 1 });
866                        }
867                    }
868
869                    show_suggestions(suggestions, "expected", Color::Red);
870                }
871            }
872            if !not_found.is_empty() {
873                writeln!(
874                    self.stdout,
875                    "\n{prefix}: {n} diagnostics expected in test file but not reported in JSON output",
876                    prefix = self.error_prefix(),
877                    n = not_found.len(),
878                );
879
880                // FIXME: Ideally, we should check this at the place where we actually parse error annotations.
881                // it's better to use (negated) heuristic inside normalize_output if possible
882                if let Some(human_format) = self.props.compile_flags.iter().find(|flag| {
883                    // `human`, `human-unicode`, `short` will not generate JSON output
884                    flag.contains("error-format")
885                        && (flag.contains("short") || flag.contains("human"))
886                }) {
887                    let msg = format!(
888                        "tests with compile flag `{}` should not have error annotations such as `//~ ERROR`",
889                        human_format
890                    ).color(Color::Red);
891                    writeln!(self.stdout, "{}", msg);
892                }
893
894                for error in &not_found {
895                    print_error(error);
896                    let mut suggestions = Vec::new();
897                    for candidate in unexpected.iter().chain(&unimportant) {
898                        let kind_mismatch = candidate.kind != error.kind;
899                        let mut push_green_suggestion = |line, msg, rank| {
900                            push_suggestion(
901                                &mut suggestions,
902                                candidate,
903                                kind_mismatch,
904                                line,
905                                msg,
906                                Color::Green,
907                                rank,
908                            )
909                        };
910                        if candidate.msg.contains(&error.msg) {
911                            push_green_suggestion(candidate.line_num != error.line_num, false, 0);
912                        } else if candidate.line_num.is_some()
913                            && candidate.line_num == error.line_num
914                        {
915                            push_green_suggestion(false, true, if kind_mismatch { 2 } else { 1 });
916                        }
917                    }
918
919                    show_suggestions(suggestions, "reported", Color::Green);
920                }
921            }
922            panic!(
923                "errors differ from expected\nstatus: {}\ncommand: {}\n",
924                proc_res.status, proc_res.cmdline
925            );
926        }
927    }
928
929    fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {
930        match (pm, self.props.fail_mode, self.config.mode) {
931            (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), TestMode::Ui) => {
932                Emit::Metadata
933            }
934            _ => Emit::None,
935        }
936    }
937
938    fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes {
939        self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), Vec::new())
940    }
941
942    fn compile_test_with_passes(
943        &self,
944        will_execute: WillExecute,
945        emit: Emit,
946        passes: Vec<String>,
947    ) -> ProcRes {
948        self.compile_test_general(will_execute, emit, self.props.local_pass_mode(), passes)
949    }
950
951    fn compile_test_general(
952        &self,
953        will_execute: WillExecute,
954        emit: Emit,
955        local_pm: Option<PassMode>,
956        passes: Vec<String>,
957    ) -> ProcRes {
958        // Only use `make_exe_name` when the test ends up being executed.
959        let output_file = match will_execute {
960            WillExecute::Yes => TargetLocation::ThisFile(self.make_exe_name()),
961            WillExecute::No | WillExecute::Disabled => {
962                TargetLocation::ThisDirectory(self.output_base_dir())
963            }
964        };
965
966        let allow_unused = match self.config.mode {
967            TestMode::Ui => {
968                // UI tests tend to have tons of unused code as
969                // it's just testing various pieces of the compile, but we don't
970                // want to actually assert warnings about all this code. Instead
971                // let's just ignore unused code warnings by defaults and tests
972                // can turn it back on if needed.
973                if !self.is_rustdoc()
974                    // Note that we use the local pass mode here as we don't want
975                    // to set unused to allow if we've overridden the pass mode
976                    // via command line flags.
977                    && local_pm != Some(PassMode::Run)
978                {
979                    AllowUnused::Yes
980                } else {
981                    AllowUnused::No
982                }
983            }
984            _ => AllowUnused::No,
985        };
986
987        let rustc = self.make_compile_args(
988            &self.testpaths.file,
989            output_file,
990            emit,
991            allow_unused,
992            LinkToAux::Yes,
993            passes,
994        );
995
996        self.compose_and_run_compiler(rustc, None)
997    }
998
999    /// `root_out_dir` and `root_testpaths` refer to the parameters of the actual test being run.
1000    /// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths.
1001    fn document(&self, root_out_dir: &Utf8Path, kind: DocKind) -> ProcRes {
1002        self.document_inner(&self.testpaths.file, root_out_dir, kind)
1003    }
1004
1005    /// Like `document`, but takes an explicit `file_to_doc` argument so that
1006    /// it can also be used for documenting auxiliaries, in addition to
1007    /// documenting the main test file.
1008    fn document_inner(
1009        &self,
1010        file_to_doc: &Utf8Path,
1011        root_out_dir: &Utf8Path,
1012        kind: DocKind,
1013    ) -> ProcRes {
1014        if self.props.build_aux_docs {
1015            assert_eq!(kind, DocKind::Html, "build-aux-docs only make sense for html output");
1016
1017            for rel_ab in &self.props.aux.builds {
1018                let aux_path = self.resolve_aux_path(rel_ab);
1019                let props_for_aux = self.props.from_aux_file(&aux_path, self.revision, self.config);
1020                let aux_cx = TestCx {
1021                    config: self.config,
1022                    stdout: self.stdout,
1023                    stderr: self.stderr,
1024                    props: &props_for_aux,
1025                    testpaths: self.testpaths,
1026                    revision: self.revision,
1027                };
1028                // Create the directory for the stdout/stderr files.
1029                create_dir_all(aux_cx.output_base_dir()).unwrap();
1030                let auxres = aux_cx.document_inner(&aux_path, &root_out_dir, kind);
1031                if !auxres.status.success() {
1032                    return auxres;
1033                }
1034            }
1035        }
1036
1037        let aux_dir = self.aux_output_dir_name();
1038
1039        let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed");
1040
1041        // actual --out-dir given to the auxiliary or test, as opposed to the root out dir for the entire
1042        // test
1043        let out_dir: Cow<'_, Utf8Path> = if self.props.unique_doc_out_dir {
1044            let file_name = file_to_doc.file_stem().expect("file name should not be empty");
1045            let out_dir = Utf8PathBuf::from_iter([
1046                root_out_dir,
1047                Utf8Path::new("docs"),
1048                Utf8Path::new(file_name),
1049                Utf8Path::new("doc"),
1050            ]);
1051            create_dir_all(&out_dir).unwrap();
1052            Cow::Owned(out_dir)
1053        } else {
1054            Cow::Borrowed(root_out_dir)
1055        };
1056
1057        let mut rustdoc = Command::new(rustdoc_path);
1058        let current_dir = self.output_base_dir();
1059        rustdoc.current_dir(current_dir);
1060        rustdoc
1061            .arg("-L")
1062            .arg(self.config.run_lib_path.as_path())
1063            .arg("-L")
1064            .arg(aux_dir)
1065            .arg("-o")
1066            .arg(out_dir.as_ref())
1067            .arg("--deny")
1068            .arg("warnings")
1069            .arg(file_to_doc)
1070            .arg("-A")
1071            .arg("internal_features")
1072            .args(&self.props.compile_flags)
1073            .args(&self.props.doc_flags);
1074
1075        match kind {
1076            DocKind::Html => {}
1077            DocKind::Json => {
1078                rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
1079            }
1080        }
1081
1082        if let Some(ref linker) = self.config.target_linker {
1083            rustdoc.arg(format!("-Clinker={}", linker));
1084        }
1085
1086        self.compose_and_run_compiler(rustdoc, None)
1087    }
1088
1089    fn exec_compiled_test(&self) -> ProcRes {
1090        self.exec_compiled_test_general(&[], true)
1091    }
1092
1093    fn exec_compiled_test_general(
1094        &self,
1095        env_extra: &[(&str, &str)],
1096        delete_after_success: bool,
1097    ) -> ProcRes {
1098        let prepare_env = |cmd: &mut Command| {
1099            for (key, val) in &self.props.exec_env {
1100                cmd.env(key, val);
1101            }
1102            for (key, val) in env_extra {
1103                cmd.env(key, val);
1104            }
1105
1106            for key in &self.props.unset_exec_env {
1107                cmd.env_remove(key);
1108            }
1109        };
1110
1111        let proc_res = match &*self.config.target {
1112            // This is pretty similar to below, we're transforming:
1113            //
1114            // ```text
1115            // program arg1 arg2
1116            // ```
1117            //
1118            // into
1119            //
1120            // ```text
1121            // remote-test-client run program 2 support-lib.so support-lib2.so arg1 arg2
1122            // ```
1123            //
1124            // The test-client program will upload `program` to the emulator along with all other
1125            // support libraries listed (in this case `support-lib.so` and `support-lib2.so`. It
1126            // will then execute the program on the emulator with the arguments specified (in the
1127            // environment we give the process) and then report back the same result.
1128            _ if self.config.remote_test_client.is_some() => {
1129                let aux_dir = self.aux_output_dir_name();
1130                let ProcArgs { prog, args } = self.make_run_args();
1131                let mut support_libs = Vec::new();
1132                if let Ok(entries) = aux_dir.read_dir() {
1133                    for entry in entries {
1134                        let entry = entry.unwrap();
1135                        if !entry.path().is_file() {
1136                            continue;
1137                        }
1138                        support_libs.push(entry.path());
1139                    }
1140                }
1141                let mut test_client =
1142                    Command::new(self.config.remote_test_client.as_ref().unwrap());
1143                test_client
1144                    .args(&["run", &support_libs.len().to_string()])
1145                    .arg(&prog)
1146                    .args(support_libs)
1147                    .args(args);
1148
1149                prepare_env(&mut test_client);
1150
1151                self.compose_and_run(
1152                    test_client,
1153                    self.config.run_lib_path.as_path(),
1154                    Some(aux_dir.as_path()),
1155                    None,
1156                )
1157            }
1158            _ if self.config.target.contains("vxworks") => {
1159                let aux_dir = self.aux_output_dir_name();
1160                let ProcArgs { prog, args } = self.make_run_args();
1161                let mut wr_run = Command::new("wr-run");
1162                wr_run.args(&[&prog]).args(args);
1163
1164                prepare_env(&mut wr_run);
1165
1166                self.compose_and_run(
1167                    wr_run,
1168                    self.config.run_lib_path.as_path(),
1169                    Some(aux_dir.as_path()),
1170                    None,
1171                )
1172            }
1173            _ => {
1174                let aux_dir = self.aux_output_dir_name();
1175                let ProcArgs { prog, args } = self.make_run_args();
1176                let mut program = Command::new(&prog);
1177                program.args(args).current_dir(&self.output_base_dir());
1178
1179                prepare_env(&mut program);
1180
1181                self.compose_and_run(
1182                    program,
1183                    self.config.run_lib_path.as_path(),
1184                    Some(aux_dir.as_path()),
1185                    None,
1186                )
1187            }
1188        };
1189
1190        if delete_after_success && proc_res.status.success() {
1191            // delete the executable after running it to save space.
1192            // it is ok if the deletion failed.
1193            let _ = fs::remove_file(self.make_exe_name());
1194        }
1195
1196        proc_res
1197    }
1198
1199    /// For each `aux-build: foo/bar` annotation, we check to find the file in an `auxiliary`
1200    /// directory relative to the test itself (not any intermediate auxiliaries).
1201    fn resolve_aux_path(&self, relative_aux_path: &str) -> Utf8PathBuf {
1202        let aux_path = self
1203            .testpaths
1204            .file
1205            .parent()
1206            .expect("test file path has no parent")
1207            .join("auxiliary")
1208            .join(relative_aux_path);
1209        if !aux_path.exists() {
1210            self.fatal(&format!(
1211                "auxiliary source file `{relative_aux_path}` not found at `{aux_path}`"
1212            ));
1213        }
1214
1215        aux_path
1216    }
1217
1218    fn is_vxworks_pure_static(&self) -> bool {
1219        if self.config.target.contains("vxworks") {
1220            match env::var("RUST_VXWORKS_TEST_DYLINK") {
1221                Ok(s) => s != "1",
1222                _ => true,
1223            }
1224        } else {
1225            false
1226        }
1227    }
1228
1229    fn is_vxworks_pure_dynamic(&self) -> bool {
1230        self.config.target.contains("vxworks") && !self.is_vxworks_pure_static()
1231    }
1232
1233    fn has_aux_dir(&self) -> bool {
1234        !self.props.aux.builds.is_empty()
1235            || !self.props.aux.crates.is_empty()
1236            || !self.props.aux.proc_macros.is_empty()
1237    }
1238
1239    fn aux_output_dir(&self) -> Utf8PathBuf {
1240        let aux_dir = self.aux_output_dir_name();
1241
1242        if !self.props.aux.builds.is_empty() {
1243            remove_and_create_dir_all(&aux_dir).unwrap_or_else(|e| {
1244                panic!("failed to remove and recreate output directory `{aux_dir}`: {e}")
1245            });
1246        }
1247
1248        if !self.props.aux.bins.is_empty() {
1249            let aux_bin_dir = self.aux_bin_output_dir_name();
1250            remove_and_create_dir_all(&aux_dir).unwrap_or_else(|e| {
1251                panic!("failed to remove and recreate output directory `{aux_dir}`: {e}")
1252            });
1253            remove_and_create_dir_all(&aux_bin_dir).unwrap_or_else(|e| {
1254                panic!("failed to remove and recreate output directory `{aux_bin_dir}`: {e}")
1255            });
1256        }
1257
1258        aux_dir
1259    }
1260
1261    fn build_all_auxiliary(&self, aux_dir: &Utf8Path, rustc: &mut Command) {
1262        for rel_ab in &self.props.aux.builds {
1263            self.build_auxiliary(rel_ab, &aux_dir, None);
1264        }
1265
1266        for rel_ab in &self.props.aux.bins {
1267            self.build_auxiliary(rel_ab, &aux_dir, Some(AuxType::Bin));
1268        }
1269
1270        let path_to_crate_name = |path: &str| -> String {
1271            path.rsplit_once('/')
1272                .map_or(path, |(_, tail)| tail)
1273                .trim_end_matches(".rs")
1274                .replace('-', "_")
1275        };
1276
1277        let add_extern =
1278            |rustc: &mut Command, aux_name: &str, aux_path: &str, aux_type: AuxType| {
1279                let lib_name = get_lib_name(&path_to_crate_name(aux_path), aux_type);
1280                if let Some(lib_name) = lib_name {
1281                    rustc.arg("--extern").arg(format!("{}={}/{}", aux_name, aux_dir, lib_name));
1282                }
1283            };
1284
1285        for (aux_name, aux_path) in &self.props.aux.crates {
1286            let aux_type = self.build_auxiliary(&aux_path, &aux_dir, None);
1287            add_extern(rustc, aux_name, aux_path, aux_type);
1288        }
1289
1290        for proc_macro in &self.props.aux.proc_macros {
1291            self.build_auxiliary(proc_macro, &aux_dir, Some(AuxType::ProcMacro));
1292            let crate_name = path_to_crate_name(proc_macro);
1293            add_extern(rustc, &crate_name, proc_macro, AuxType::ProcMacro);
1294        }
1295
1296        // Build any `//@ aux-codegen-backend`, and pass the resulting library
1297        // to `-Zcodegen-backend` when compiling the test file.
1298        if let Some(aux_file) = &self.props.aux.codegen_backend {
1299            let aux_type = self.build_auxiliary(aux_file, aux_dir, None);
1300            if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
1301                let lib_path = aux_dir.join(&lib_name);
1302                rustc.arg(format!("-Zcodegen-backend={}", lib_path));
1303            }
1304        }
1305    }
1306
1307    /// `root_testpaths` refers to the path of the original test. the auxiliary and the test with an
1308    /// aux-build have the same `root_testpaths`.
1309    fn compose_and_run_compiler(&self, mut rustc: Command, input: Option<String>) -> ProcRes {
1310        if self.props.add_minicore {
1311            let minicore_path = self.build_minicore();
1312            rustc.arg("--extern");
1313            rustc.arg(&format!("minicore={}", minicore_path));
1314        }
1315
1316        let aux_dir = self.aux_output_dir();
1317        self.build_all_auxiliary(&aux_dir, &mut rustc);
1318
1319        rustc.envs(self.props.rustc_env.clone());
1320        self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
1321        self.compose_and_run(
1322            rustc,
1323            self.config.compile_lib_path.as_path(),
1324            Some(aux_dir.as_path()),
1325            input,
1326        )
1327    }
1328
1329    /// Builds `minicore`. Returns the path to the minicore rlib within the base test output
1330    /// directory.
1331    fn build_minicore(&self) -> Utf8PathBuf {
1332        let output_file_path = self.output_base_dir().join("libminicore.rlib");
1333        let mut rustc = self.make_compile_args(
1334            &self.config.minicore_path,
1335            TargetLocation::ThisFile(output_file_path.clone()),
1336            Emit::None,
1337            AllowUnused::Yes,
1338            LinkToAux::No,
1339            vec![],
1340        );
1341
1342        rustc.args(&["--crate-type", "rlib"]);
1343        rustc.arg("-Cpanic=abort");
1344        rustc.args(self.props.minicore_compile_flags.clone());
1345
1346        let res = self.compose_and_run(rustc, self.config.compile_lib_path.as_path(), None, None);
1347        if !res.status.success() {
1348            self.fatal_proc_rec(
1349                &format!("auxiliary build of {} failed to compile: ", self.config.minicore_path),
1350                &res,
1351            );
1352        }
1353
1354        output_file_path
1355    }
1356
1357    /// Builds an aux dependency.
1358    ///
1359    /// If `aux_type` is `None`, then this will determine the aux-type automatically.
1360    fn build_auxiliary(
1361        &self,
1362        source_path: &str,
1363        aux_dir: &Utf8Path,
1364        aux_type: Option<AuxType>,
1365    ) -> AuxType {
1366        let aux_path = self.resolve_aux_path(source_path);
1367        let mut aux_props = self.props.from_aux_file(&aux_path, self.revision, self.config);
1368        if aux_type == Some(AuxType::ProcMacro) {
1369            aux_props.force_host = true;
1370        }
1371        let mut aux_dir = aux_dir.to_path_buf();
1372        if aux_type == Some(AuxType::Bin) {
1373            // On unix, the binary of `auxiliary/foo.rs` will be named
1374            // `auxiliary/foo` which clashes with the _dir_ `auxiliary/foo`, so
1375            // put bins in a `bin` subfolder.
1376            aux_dir.push("bin");
1377        }
1378        let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1379        let aux_cx = TestCx {
1380            config: self.config,
1381            stdout: self.stdout,
1382            stderr: self.stderr,
1383            props: &aux_props,
1384            testpaths: self.testpaths,
1385            revision: self.revision,
1386        };
1387        // Create the directory for the stdout/stderr files.
1388        create_dir_all(aux_cx.output_base_dir()).unwrap();
1389        let mut aux_rustc = aux_cx.make_compile_args(
1390            &aux_path,
1391            aux_output,
1392            Emit::None,
1393            AllowUnused::No,
1394            LinkToAux::No,
1395            Vec::new(),
1396        );
1397        aux_cx.build_all_auxiliary(&aux_dir, &mut aux_rustc);
1398
1399        aux_rustc.envs(aux_props.rustc_env.clone());
1400        for key in &aux_props.unset_rustc_env {
1401            aux_rustc.env_remove(key);
1402        }
1403
1404        let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1405            (AuxType::Bin, Some("bin"))
1406        } else if aux_type == Some(AuxType::ProcMacro) {
1407            (AuxType::ProcMacro, Some("proc-macro"))
1408        } else if aux_type.is_some() {
1409            panic!("aux_type {aux_type:?} not expected");
1410        } else if aux_props.no_prefer_dynamic {
1411            (AuxType::Dylib, None)
1412        } else if self.config.target.contains("emscripten")
1413            || (self.config.target.contains("musl")
1414                && !aux_props.force_host
1415                && !self.config.host.contains("musl"))
1416            || self.config.target.contains("wasm32")
1417            || self.config.target.contains("nvptx")
1418            || self.is_vxworks_pure_static()
1419            || self.config.target.contains("bpf")
1420            || !self.config.target_cfg().dynamic_linking
1421            || matches!(self.config.mode, TestMode::CoverageMap | TestMode::CoverageRun)
1422        {
1423            // We primarily compile all auxiliary libraries as dynamic libraries
1424            // to avoid code size bloat and large binaries as much as possible
1425            // for the test suite (otherwise including libstd statically in all
1426            // executables takes up quite a bit of space).
1427            //
1428            // For targets like MUSL or Emscripten, however, there is no support for
1429            // dynamic libraries so we just go back to building a normal library. Note,
1430            // however, that for MUSL if the library is built with `force_host` then
1431            // it's ok to be a dylib as the host should always support dylibs.
1432            //
1433            // Coverage tests want static linking by default so that coverage
1434            // mappings in auxiliary libraries can be merged into the final
1435            // executable.
1436            (AuxType::Lib, Some("lib"))
1437        } else {
1438            (AuxType::Dylib, Some("dylib"))
1439        };
1440
1441        if let Some(crate_type) = crate_type {
1442            aux_rustc.args(&["--crate-type", crate_type]);
1443        }
1444
1445        if aux_type == AuxType::ProcMacro {
1446            // For convenience, but this only works on 2018.
1447            aux_rustc.args(&["--extern", "proc_macro"]);
1448        }
1449
1450        aux_rustc.arg("-L").arg(&aux_dir);
1451
1452        if aux_props.add_minicore {
1453            let minicore_path = self.build_minicore();
1454            aux_rustc.arg("--extern");
1455            aux_rustc.arg(&format!("minicore={}", minicore_path));
1456        }
1457
1458        let auxres = aux_cx.compose_and_run(
1459            aux_rustc,
1460            aux_cx.config.compile_lib_path.as_path(),
1461            Some(aux_dir.as_path()),
1462            None,
1463        );
1464        if !auxres.status.success() {
1465            self.fatal_proc_rec(
1466                &format!("auxiliary build of {aux_path} failed to compile: "),
1467                &auxres,
1468            );
1469        }
1470        aux_type
1471    }
1472
1473    fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1474        let mut filter_paths_from_len = Vec::new();
1475        let mut add_path = |path: &Utf8Path| {
1476            let path = path.to_string();
1477            let windows = path.replace("\\", "\\\\");
1478            if windows != path {
1479                filter_paths_from_len.push(windows);
1480            }
1481            filter_paths_from_len.push(path);
1482        };
1483
1484        // List of paths that will not be measured when determining whether the output is larger
1485        // than the output truncation threshold.
1486        //
1487        // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the
1488        // same slice of text will be double counted and the truncation might not happen.
1489        add_path(&self.config.src_test_suite_root);
1490        add_path(&self.config.build_test_suite_root);
1491
1492        read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1493    }
1494
1495    fn compose_and_run(
1496        &self,
1497        mut command: Command,
1498        lib_path: &Utf8Path,
1499        aux_path: Option<&Utf8Path>,
1500        input: Option<String>,
1501    ) -> ProcRes {
1502        let cmdline = {
1503            let cmdline = self.make_cmdline(&command, lib_path);
1504            self.logv(format_args!("executing {cmdline}"));
1505            cmdline
1506        };
1507
1508        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1509
1510        // Need to be sure to put both the lib_path and the aux path in the dylib
1511        // search path for the child.
1512        add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1513
1514        let mut child = disable_error_reporting(|| command.spawn())
1515            .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1516        if let Some(input) = input {
1517            child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1518        }
1519
1520        let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1521
1522        let result = ProcRes {
1523            status,
1524            stdout: String::from_utf8_lossy(&stdout).into_owned(),
1525            stderr: String::from_utf8_lossy(&stderr).into_owned(),
1526            truncated,
1527            cmdline,
1528        };
1529
1530        self.dump_output(
1531            self.config.verbose || (!result.status.success() && self.config.mode != TestMode::Ui),
1532            &command.get_program().to_string_lossy(),
1533            &result.stdout,
1534            &result.stderr,
1535        );
1536
1537        result
1538    }
1539
1540    fn is_rustdoc(&self) -> bool {
1541        matches!(
1542            self.config.suite,
1543            TestSuite::RustdocUi | TestSuite::RustdocJs | TestSuite::RustdocJson
1544        )
1545    }
1546
1547    fn make_compile_args(
1548        &self,
1549        input_file: &Utf8Path,
1550        output_file: TargetLocation,
1551        emit: Emit,
1552        allow_unused: AllowUnused,
1553        link_to_aux: LinkToAux,
1554        passes: Vec<String>, // Vec of passes under mir-opt test to be dumped
1555    ) -> Command {
1556        let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
1557        let is_rustdoc = self.is_rustdoc() && !is_aux;
1558        let mut rustc = if !is_rustdoc {
1559            Command::new(&self.config.rustc_path)
1560        } else {
1561            Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1562        };
1563        rustc.arg(input_file);
1564
1565        // Use a single thread for efficiency and a deterministic error message order
1566        rustc.arg("-Zthreads=1");
1567
1568        // Hide libstd sources from ui tests to make sure we generate the stderr
1569        // output that users will see.
1570        // Without this, we may be producing good diagnostics in-tree but users
1571        // will not see half the information.
1572        //
1573        // This also has the benefit of more effectively normalizing output between different
1574        // compilers, so that we don't have to know the `/rustc/$sha` output to normalize after the
1575        // fact.
1576        rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1577        rustc.arg("-Ztranslate-remapped-path-to-local-path=no");
1578
1579        // Hide Cargo dependency sources from ui tests to make sure the error message doesn't
1580        // change depending on whether $CARGO_HOME is remapped or not. If this is not present,
1581        // when $CARGO_HOME is remapped the source won't be shown, and when it's not remapped the
1582        // source will be shown, causing a blessing hell.
1583        rustc.arg("-Z").arg(format!(
1584            "ignore-directory-in-diagnostics-source-blocks={}",
1585            home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1586        ));
1587        // Similarly, vendored sources shouldn't be shown when running from a dist tarball.
1588        rustc.arg("-Z").arg(format!(
1589            "ignore-directory-in-diagnostics-source-blocks={}",
1590            self.config.src_root.join("vendor"),
1591        ));
1592
1593        // Optionally prevent default --sysroot if specified in test compile-flags.
1594        //
1595        // FIXME: I feel like this logic is fairly sus.
1596        if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1597            && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1598        {
1599            // In stage 0, make sure we use `stage0-sysroot` instead of the bootstrap sysroot.
1600            rustc.arg("--sysroot").arg(&self.config.sysroot_base);
1601        }
1602
1603        // If the provided codegen backend is not LLVM, we need to pass it.
1604        if let Some(ref backend) = self.config.override_codegen_backend {
1605            rustc.arg(format!("-Zcodegen-backend={}", backend));
1606        }
1607
1608        // Optionally prevent default --target if specified in test compile-flags.
1609        let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1610
1611        if !custom_target {
1612            let target =
1613                if self.props.force_host { &*self.config.host } else { &*self.config.target };
1614
1615            rustc.arg(&format!("--target={}", target));
1616        }
1617        self.set_revision_flags(&mut rustc);
1618
1619        if !is_rustdoc {
1620            if let Some(ref incremental_dir) = self.props.incremental_dir {
1621                rustc.args(&["-C", &format!("incremental={}", incremental_dir)]);
1622                rustc.args(&["-Z", "incremental-verify-ich"]);
1623            }
1624
1625            if self.config.mode == TestMode::CodegenUnits {
1626                rustc.args(&["-Z", "human_readable_cgu_names"]);
1627            }
1628        }
1629
1630        if self.config.optimize_tests && !is_rustdoc {
1631            match self.config.mode {
1632                TestMode::Ui => {
1633                    // If optimize-tests is true we still only want to optimize tests that actually get
1634                    // executed and that don't specify their own optimization levels.
1635                    // Note: aux libs don't have a pass-mode, so they won't get optimized
1636                    // unless compile-flags are set in the aux file.
1637                    if self.config.optimize_tests
1638                        && self.props.pass_mode(&self.config) == Some(PassMode::Run)
1639                        && !self
1640                            .props
1641                            .compile_flags
1642                            .iter()
1643                            .any(|arg| arg == "-O" || arg.contains("opt-level"))
1644                    {
1645                        rustc.arg("-O");
1646                    }
1647                }
1648                TestMode::DebugInfo => { /* debuginfo tests must be unoptimized */ }
1649                TestMode::CoverageMap | TestMode::CoverageRun => {
1650                    // Coverage mappings and coverage reports are affected by
1651                    // optimization level, so they ignore the optimize-tests
1652                    // setting and set an optimization level in their mode's
1653                    // compile flags (below) or in per-test `compile-flags`.
1654                }
1655                _ => {
1656                    rustc.arg("-O");
1657                }
1658            }
1659        }
1660
1661        let set_mir_dump_dir = |rustc: &mut Command| {
1662            let mir_dump_dir = self.output_base_dir();
1663            let mut dir_opt = "-Zdump-mir-dir=".to_string();
1664            dir_opt.push_str(mir_dump_dir.as_str());
1665            debug!("dir_opt: {:?}", dir_opt);
1666            rustc.arg(dir_opt);
1667        };
1668
1669        match self.config.mode {
1670            TestMode::Incremental => {
1671                // If we are extracting and matching errors in the new
1672                // fashion, then you want JSON mode. Old-skool error
1673                // patterns still match the raw compiler output.
1674                if self.props.error_patterns.is_empty()
1675                    && self.props.regex_error_patterns.is_empty()
1676                {
1677                    rustc.args(&["--error-format", "json"]);
1678                    rustc.args(&["--json", "future-incompat"]);
1679                }
1680                rustc.arg("-Zui-testing");
1681                rustc.arg("-Zdeduplicate-diagnostics=no");
1682            }
1683            TestMode::Ui => {
1684                if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1685                    rustc.args(&["--error-format", "json"]);
1686                    rustc.args(&["--json", "future-incompat"]);
1687                }
1688                rustc.arg("-Ccodegen-units=1");
1689                // Hide line numbers to reduce churn
1690                rustc.arg("-Zui-testing");
1691                rustc.arg("-Zdeduplicate-diagnostics=no");
1692                rustc.arg("-Zwrite-long-types-to-disk=no");
1693                // FIXME: use this for other modes too, for perf?
1694                rustc.arg("-Cstrip=debuginfo");
1695            }
1696            TestMode::MirOpt => {
1697                // We check passes under test to minimize the mir-opt test dump
1698                // if files_for_miropt_test parses the passes, we dump only those passes
1699                // otherwise we conservatively pass -Zdump-mir=all
1700                let zdump_arg = if !passes.is_empty() {
1701                    format!("-Zdump-mir={}", passes.join(" | "))
1702                } else {
1703                    "-Zdump-mir=all".to_string()
1704                };
1705
1706                rustc.args(&[
1707                    "-Copt-level=1",
1708                    &zdump_arg,
1709                    "-Zvalidate-mir",
1710                    "-Zlint-mir",
1711                    "-Zdump-mir-exclude-pass-number",
1712                    "-Zmir-include-spans=false", // remove span comments from NLL MIR dumps
1713                    "--crate-type=rlib",
1714                ]);
1715                if let Some(pass) = &self.props.mir_unit_test {
1716                    rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1717                } else {
1718                    rustc.args(&[
1719                        "-Zmir-opt-level=4",
1720                        "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1721                    ]);
1722                }
1723
1724                set_mir_dump_dir(&mut rustc);
1725            }
1726            TestMode::CoverageMap => {
1727                rustc.arg("-Cinstrument-coverage");
1728                // These tests only compile to LLVM IR, so they don't need the
1729                // profiler runtime to be present.
1730                rustc.arg("-Zno-profiler-runtime");
1731                // Coverage mappings are sensitive to MIR optimizations, and
1732                // the current snapshots assume `opt-level=2` unless overridden
1733                // by `compile-flags`.
1734                rustc.arg("-Copt-level=2");
1735            }
1736            TestMode::CoverageRun => {
1737                rustc.arg("-Cinstrument-coverage");
1738                // Coverage reports are sometimes sensitive to optimizations,
1739                // and the current snapshots assume `opt-level=2` unless
1740                // overridden by `compile-flags`.
1741                rustc.arg("-Copt-level=2");
1742            }
1743            TestMode::Assembly | TestMode::Codegen => {
1744                rustc.arg("-Cdebug-assertions=no");
1745                // For assembly and codegen tests, we want to use the same order
1746                // of the items of a codegen unit as the source order, so that
1747                // we can compare the output with the source code through filecheck.
1748                rustc.arg("-Zcodegen-source-order");
1749            }
1750            TestMode::Crashes => {
1751                set_mir_dump_dir(&mut rustc);
1752            }
1753            TestMode::CodegenUnits => {
1754                rustc.arg("-Zprint-mono-items");
1755            }
1756            TestMode::Pretty
1757            | TestMode::DebugInfo
1758            | TestMode::Rustdoc
1759            | TestMode::RustdocJson
1760            | TestMode::RunMake
1761            | TestMode::RustdocJs => {
1762                // do not use JSON output
1763            }
1764        }
1765
1766        if self.props.remap_src_base {
1767            rustc.arg(format!(
1768                "--remap-path-prefix={}={}",
1769                self.config.src_test_suite_root, FAKE_SRC_BASE,
1770            ));
1771        }
1772
1773        match emit {
1774            Emit::None => {}
1775            Emit::Metadata if is_rustdoc => {}
1776            Emit::Metadata => {
1777                rustc.args(&["--emit", "metadata"]);
1778            }
1779            Emit::LlvmIr => {
1780                rustc.args(&["--emit", "llvm-ir"]);
1781            }
1782            Emit::Mir => {
1783                rustc.args(&["--emit", "mir"]);
1784            }
1785            Emit::Asm => {
1786                rustc.args(&["--emit", "asm"]);
1787            }
1788            Emit::LinkArgsAsm => {
1789                rustc.args(&["-Clink-args=--emit=asm"]);
1790            }
1791        }
1792
1793        if !is_rustdoc {
1794            if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1795                // rustc.arg("-g"); // get any backtrace at all on errors
1796            } else if !self.props.no_prefer_dynamic {
1797                rustc.args(&["-C", "prefer-dynamic"]);
1798            }
1799        }
1800
1801        match output_file {
1802            // If the test's compile flags specify an output path with `-o`,
1803            // avoid a compiler warning about `--out-dir` being ignored.
1804            _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1805            TargetLocation::ThisFile(path) => {
1806                rustc.arg("-o").arg(path);
1807            }
1808            TargetLocation::ThisDirectory(path) => {
1809                if is_rustdoc {
1810                    // `rustdoc` uses `-o` for the output directory.
1811                    rustc.arg("-o").arg(path);
1812                } else {
1813                    rustc.arg("--out-dir").arg(path);
1814                }
1815            }
1816        }
1817
1818        match self.config.compare_mode {
1819            Some(CompareMode::Polonius) => {
1820                rustc.args(&["-Zpolonius=next"]);
1821            }
1822            Some(CompareMode::NextSolver) => {
1823                rustc.args(&["-Znext-solver"]);
1824            }
1825            Some(CompareMode::NextSolverCoherence) => {
1826                rustc.args(&["-Znext-solver=coherence"]);
1827            }
1828            Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1829                rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1830            }
1831            Some(CompareMode::SplitDwarf) => {
1832                rustc.args(&["-Csplit-debuginfo=unpacked"]);
1833            }
1834            Some(CompareMode::SplitDwarfSingle) => {
1835                rustc.args(&["-Csplit-debuginfo=packed"]);
1836            }
1837            None => {}
1838        }
1839
1840        // Add `-A unused` before `config` flags and in-test (`props`) flags, so that they can
1841        // overwrite this.
1842        // Don't allow `unused_attributes` since these are usually actual mistakes, rather than just unused code.
1843        if let AllowUnused::Yes = allow_unused {
1844            rustc.args(&["-A", "unused", "-W", "unused_attributes"]);
1845        }
1846
1847        // Allow tests to use internal features.
1848        rustc.args(&["-A", "internal_features"]);
1849
1850        // Allow tests to have unused parens and braces.
1851        // Add #![deny(unused_parens, unused_braces)] to the test file if you want to
1852        // test that these lints are working.
1853        rustc.args(&["-A", "unused_parens"]);
1854        rustc.args(&["-A", "unused_braces"]);
1855
1856        if self.props.force_host {
1857            self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1858            if !is_rustdoc {
1859                if let Some(ref linker) = self.config.host_linker {
1860                    rustc.arg(format!("-Clinker={}", linker));
1861                }
1862            }
1863        } else {
1864            self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1865            if !is_rustdoc {
1866                if let Some(ref linker) = self.config.target_linker {
1867                    rustc.arg(format!("-Clinker={}", linker));
1868                }
1869            }
1870        }
1871
1872        // Use dynamic musl for tests because static doesn't allow creating dylibs
1873        if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1874            rustc.arg("-Ctarget-feature=-crt-static");
1875        }
1876
1877        if let LinkToAux::Yes = link_to_aux {
1878            // if we pass an `-L` argument to a directory that doesn't exist,
1879            // macOS ld emits warnings which disrupt the .stderr files
1880            if self.has_aux_dir() {
1881                rustc.arg("-L").arg(self.aux_output_dir_name());
1882            }
1883        }
1884
1885        // FIXME(jieyouxu): we should report a fatal error or warning if user wrote `-Cpanic=` with
1886        // something that's not `abort` and `-Cforce-unwind-tables` with a value that is not `yes`.
1887        //
1888        // We could apply these last and override any provided flags. That would ensure that the
1889        // build works, but some tests want to exercise that mixing panic modes in specific ways is
1890        // rejected. So we enable aborting panics and unwind tables before adding flags, just to
1891        // change the default.
1892        //
1893        // `minicore` requires `#![no_std]` and `#![no_core]`, which means no unwinding panics.
1894        if self.props.add_minicore {
1895            rustc.arg("-Cpanic=abort");
1896            rustc.arg("-Cforce-unwind-tables=yes");
1897        }
1898
1899        rustc.args(&self.props.compile_flags);
1900
1901        rustc
1902    }
1903
1904    fn make_exe_name(&self) -> Utf8PathBuf {
1905        // Using a single letter here to keep the path length down for
1906        // Windows.  Some test names get very long.  rustc creates `rcgu`
1907        // files with the module name appended to it which can more than
1908        // double the length.
1909        let mut f = self.output_base_dir().join("a");
1910        // FIXME: This is using the host architecture exe suffix, not target!
1911        if self.config.target.contains("emscripten") {
1912            f = f.with_extra_extension("js");
1913        } else if self.config.target.starts_with("wasm") {
1914            f = f.with_extra_extension("wasm");
1915        } else if self.config.target.contains("spirv") {
1916            f = f.with_extra_extension("spv");
1917        } else if !env::consts::EXE_SUFFIX.is_empty() {
1918            f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1919        }
1920        f
1921    }
1922
1923    fn make_run_args(&self) -> ProcArgs {
1924        // If we've got another tool to run under (valgrind),
1925        // then split apart its command
1926        let mut args = self.split_maybe_args(&self.config.runner);
1927
1928        let exe_file = self.make_exe_name();
1929
1930        args.push(exe_file.into_os_string());
1931
1932        // Add the arguments in the run_flags directive
1933        args.extend(self.props.run_flags.iter().map(OsString::from));
1934
1935        let prog = args.remove(0);
1936        ProcArgs { prog, args }
1937    }
1938
1939    fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1940        match *argstr {
1941            Some(ref s) => s
1942                .split(' ')
1943                .filter_map(|s| {
1944                    if s.chars().all(|c| c.is_whitespace()) {
1945                        None
1946                    } else {
1947                        Some(OsString::from(s))
1948                    }
1949                })
1950                .collect(),
1951            None => Vec::new(),
1952        }
1953    }
1954
1955    fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
1956        use crate::util;
1957
1958        // Linux and mac don't require adjusting the library search path
1959        if cfg!(unix) {
1960            format!("{:?}", command)
1961        } else {
1962            // Build the LD_LIBRARY_PATH variable as it would be seen on the command line
1963            // for diagnostic purposes
1964            fn lib_path_cmd_prefix(path: &str) -> String {
1965                format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1966            }
1967
1968            format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
1969        }
1970    }
1971
1972    fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1973        let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1974
1975        self.dump_output_file(out, &format!("{}out", revision));
1976        self.dump_output_file(err, &format!("{}err", revision));
1977
1978        if !print_output {
1979            return;
1980        }
1981
1982        let path = Utf8Path::new(proc_name);
1983        let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1984            String::from_iter(
1985                path.parent()
1986                    .unwrap()
1987                    .file_name()
1988                    .into_iter()
1989                    .chain(Some("/"))
1990                    .chain(path.file_name()),
1991            )
1992        } else {
1993            path.file_name().unwrap().into()
1994        };
1995        writeln!(self.stdout, "------{proc_name} stdout------------------------------");
1996        writeln!(self.stdout, "{}", out);
1997        writeln!(self.stdout, "------{proc_name} stderr------------------------------");
1998        writeln!(self.stdout, "{}", err);
1999        writeln!(self.stdout, "------------------------------------------");
2000    }
2001
2002    fn dump_output_file(&self, out: &str, extension: &str) {
2003        let outfile = self.make_out_name(extension);
2004        fs::write(outfile.as_std_path(), out)
2005            .unwrap_or_else(|err| panic!("failed to write {outfile}: {err:?}"));
2006    }
2007
2008    /// Creates a filename for output with the given extension.
2009    /// E.g., `/.../testname.revision.mode/testname.extension`.
2010    fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
2011        self.output_base_name().with_extension(extension)
2012    }
2013
2014    /// Gets the directory where auxiliary files are written.
2015    /// E.g., `/.../testname.revision.mode/auxiliary/`.
2016    fn aux_output_dir_name(&self) -> Utf8PathBuf {
2017        self.output_base_dir()
2018            .join("auxiliary")
2019            .with_extra_extension(self.config.mode.aux_dir_disambiguator())
2020    }
2021
2022    /// Gets the directory where auxiliary binaries are written.
2023    /// E.g., `/.../testname.revision.mode/auxiliary/bin`.
2024    fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
2025        self.aux_output_dir_name().join("bin")
2026    }
2027
2028    /// The revision, ignored for incremental compilation since it wants all revisions in
2029    /// the same directory.
2030    fn safe_revision(&self) -> Option<&str> {
2031        if self.config.mode == TestMode::Incremental { None } else { self.revision }
2032    }
2033
2034    /// Gets the absolute path to the directory where all output for the given
2035    /// test/revision should reside.
2036    /// E.g., `/path/to/build/host-tuple/test/ui/relative/testname.revision.mode/`.
2037    fn output_base_dir(&self) -> Utf8PathBuf {
2038        output_base_dir(self.config, self.testpaths, self.safe_revision())
2039    }
2040
2041    /// Gets the absolute path to the base filename used as output for the given
2042    /// test/revision.
2043    /// E.g., `/.../relative/testname.revision.mode/testname`.
2044    fn output_base_name(&self) -> Utf8PathBuf {
2045        output_base_name(self.config, self.testpaths, self.safe_revision())
2046    }
2047
2048    /// Prints a message to (captured) stdout if `config.verbose` is true.
2049    /// The message is also logged to `tracing::debug!` regardles of verbosity.
2050    ///
2051    /// Use `format_args!` as the argument to perform formatting if required.
2052    fn logv(&self, message: impl fmt::Display) {
2053        debug!("{message}");
2054        if self.config.verbose {
2055            // Note: `./x test ... --verbose --no-capture` is needed to see this print.
2056            writeln!(self.stdout, "{message}");
2057        }
2058    }
2059
2060    /// Prefix to print before error messages. Normally just `error`, but also
2061    /// includes the revision name for tests that use revisions.
2062    #[must_use]
2063    fn error_prefix(&self) -> String {
2064        match self.revision {
2065            Some(rev) => format!("error in revision `{rev}`"),
2066            None => format!("error"),
2067        }
2068    }
2069
2070    #[track_caller]
2071    fn fatal(&self, err: &str) -> ! {
2072        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2073        error!("fatal error, panic: {:?}", err);
2074        panic!("fatal error");
2075    }
2076
2077    fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
2078        self.fatal_proc_rec_general(err, None, proc_res, || ());
2079    }
2080
2081    /// Underlying implementation of [`Self::fatal_proc_rec`], providing some
2082    /// extra capabilities not needed by most callers.
2083    fn fatal_proc_rec_general(
2084        &self,
2085        err: &str,
2086        extra_note: Option<&str>,
2087        proc_res: &ProcRes,
2088        callback_before_unwind: impl FnOnce(),
2089    ) -> ! {
2090        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2091
2092        // Some callers want to print additional notes after the main error message.
2093        if let Some(note) = extra_note {
2094            writeln!(self.stdout, "{note}");
2095        }
2096
2097        // Print the details and output of the subprocess that caused this test to fail.
2098        writeln!(self.stdout, "{}", proc_res.format_info());
2099
2100        // Some callers want print more context or show a custom diff before the unwind occurs.
2101        callback_before_unwind();
2102
2103        // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from
2104        // compiletest, which is unnecessary noise.
2105        std::panic::resume_unwind(Box::new(()));
2106    }
2107
2108    // codegen tests (using FileCheck)
2109
2110    fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
2111        let output_path = self.output_base_name().with_extension("ll");
2112        let input_file = &self.testpaths.file;
2113        let rustc = self.make_compile_args(
2114            input_file,
2115            TargetLocation::ThisFile(output_path.clone()),
2116            Emit::LlvmIr,
2117            AllowUnused::No,
2118            LinkToAux::Yes,
2119            Vec::new(),
2120        );
2121
2122        let proc_res = self.compose_and_run_compiler(rustc, None);
2123        (proc_res, output_path)
2124    }
2125
2126    fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
2127        let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
2128        filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
2129
2130        // Because we use custom prefixes, we also have to register the default prefix.
2131        filecheck.arg("--check-prefix=CHECK");
2132
2133        // FIXME(#134510): auto-registering revision names as check prefix is a bit sketchy, and
2134        // that having to pass `--allow-unused-prefix` is an unfortunate side-effect of not knowing
2135        // whether the test author actually wanted revision-specific check prefixes or not.
2136        //
2137        // TL;DR We may not want to conflate `compiletest` revisions and `FileCheck` prefixes.
2138
2139        // HACK: tests are allowed to use a revision name as a check prefix.
2140        if let Some(rev) = self.revision {
2141            filecheck.arg("--check-prefix").arg(rev);
2142        }
2143
2144        // HACK: the filecheck tool normally fails if a prefix is defined but not used. However,
2145        // sometimes revisions are used to specify *compiletest* directives which are not FileCheck
2146        // concerns.
2147        filecheck.arg("--allow-unused-prefixes");
2148
2149        // Provide more context on failures.
2150        filecheck.args(&["--dump-input-context", "100"]);
2151
2152        // Add custom flags supplied by the `filecheck-flags:` test directive.
2153        filecheck.args(&self.props.filecheck_flags);
2154
2155        // FIXME(jieyouxu): don't pass an empty Path
2156        self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
2157    }
2158
2159    fn charset() -> &'static str {
2160        // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset
2161        if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
2162    }
2163
2164    fn compare_to_default_rustdoc(&self, out_dir: &Utf8Path) {
2165        if !self.config.has_html_tidy {
2166            return;
2167        }
2168        writeln!(self.stdout, "info: generating a diff against nightly rustdoc");
2169
2170        let suffix =
2171            self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2172        let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2173        remove_and_create_dir_all(&compare_dir).unwrap_or_else(|e| {
2174            panic!("failed to remove and recreate output directory `{compare_dir}`: {e}")
2175        });
2176
2177        // We need to create a new struct for the lifetimes on `config` to work.
2178        let new_rustdoc = TestCx {
2179            config: &Config {
2180                // FIXME: use beta or a user-specified rustdoc instead of
2181                // hardcoding the default toolchain
2182                rustdoc_path: Some("rustdoc".into()),
2183                // Needed for building auxiliary docs below
2184                rustc_path: "rustc".into(),
2185                ..self.config.clone()
2186            },
2187            ..*self
2188        };
2189
2190        let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2191        let mut rustc = new_rustdoc.make_compile_args(
2192            &new_rustdoc.testpaths.file,
2193            output_file,
2194            Emit::None,
2195            AllowUnused::Yes,
2196            LinkToAux::Yes,
2197            Vec::new(),
2198        );
2199        let aux_dir = new_rustdoc.aux_output_dir();
2200        new_rustdoc.build_all_auxiliary(&aux_dir, &mut rustc);
2201
2202        let proc_res = new_rustdoc.document(&compare_dir, DocKind::Html);
2203        if !proc_res.status.success() {
2204            writeln!(self.stderr, "failed to run nightly rustdoc");
2205            return;
2206        }
2207
2208        #[rustfmt::skip]
2209        let tidy_args = [
2210            "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar,rustdoc-topbar",
2211            "--indent", "yes",
2212            "--indent-spaces", "2",
2213            "--wrap", "0",
2214            "--show-warnings", "no",
2215            "--markup", "yes",
2216            "--quiet", "yes",
2217            "-modify",
2218        ];
2219        let tidy_dir = |dir| {
2220            for entry in walkdir::WalkDir::new(dir) {
2221                let entry = entry.expect("failed to read file");
2222                if entry.file_type().is_file()
2223                    && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2224                {
2225                    let status =
2226                        Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2227                    // `tidy` returns 1 if it modified the file.
2228                    assert!(status.success() || status.code() == Some(1));
2229                }
2230            }
2231        };
2232        tidy_dir(out_dir);
2233        tidy_dir(&compare_dir);
2234
2235        let pager = {
2236            let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2237            output.and_then(|out| {
2238                if out.status.success() {
2239                    Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2240                } else {
2241                    None
2242                }
2243            })
2244        };
2245
2246        let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2247
2248        if !write_filtered_diff(
2249            self,
2250            &diff_filename,
2251            out_dir,
2252            &compare_dir,
2253            self.config.verbose,
2254            |file_type, extension| {
2255                file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2256            },
2257        ) {
2258            return;
2259        }
2260
2261        match self.config.color {
2262            ColorConfig::AlwaysColor => colored::control::set_override(true),
2263            ColorConfig::NeverColor => colored::control::set_override(false),
2264            _ => {}
2265        }
2266
2267        if let Some(pager) = pager {
2268            let pager = pager.trim();
2269            if self.config.verbose {
2270                writeln!(self.stderr, "using pager {}", pager);
2271            }
2272            let output = Command::new(pager)
2273                // disable paging; we want this to be non-interactive
2274                .env("PAGER", "")
2275                .stdin(File::open(&diff_filename).unwrap())
2276                // Capture output and print it explicitly so it will in turn be
2277                // captured by output-capture.
2278                .output()
2279                .unwrap();
2280            assert!(output.status.success());
2281            writeln!(self.stdout, "{}", String::from_utf8_lossy(&output.stdout));
2282            writeln!(self.stderr, "{}", String::from_utf8_lossy(&output.stderr));
2283        } else {
2284            warning!("no pager configured, falling back to unified diff");
2285            help!(
2286                "try configuring a git pager (e.g. `delta`) with \
2287                `git config --global core.pager delta`"
2288            );
2289            let mut out = io::stdout();
2290            let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2291            let mut line = Vec::new();
2292            loop {
2293                line.truncate(0);
2294                match diff.read_until(b'\n', &mut line) {
2295                    Ok(0) => break,
2296                    Ok(_) => {}
2297                    Err(e) => writeln!(self.stderr, "ERROR: {:?}", e),
2298                }
2299                match String::from_utf8(line.clone()) {
2300                    Ok(line) => {
2301                        if line.starts_with('+') {
2302                            write!(&mut out, "{}", line.green()).unwrap();
2303                        } else if line.starts_with('-') {
2304                            write!(&mut out, "{}", line.red()).unwrap();
2305                        } else if line.starts_with('@') {
2306                            write!(&mut out, "{}", line.blue()).unwrap();
2307                        } else {
2308                            out.write_all(line.as_bytes()).unwrap();
2309                        }
2310                    }
2311                    Err(_) => {
2312                        write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2313                    }
2314                }
2315            }
2316        };
2317    }
2318
2319    fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
2320        let content = fs::read_to_string(path.as_std_path()).unwrap();
2321        let mut ignore = false;
2322        content
2323            .lines()
2324            .enumerate()
2325            .filter_map(|(line_nb, line)| {
2326                if (line.trim_start().starts_with("pub mod ")
2327                    || line.trim_start().starts_with("mod "))
2328                    && line.ends_with(';')
2329                {
2330                    if let Some(ref mut other_files) = other_files {
2331                        other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2332                    }
2333                    None
2334                } else {
2335                    let sline = line.rsplit("///").next().unwrap();
2336                    let line = sline.trim_start();
2337                    if line.starts_with("```") {
2338                        if ignore {
2339                            ignore = false;
2340                            None
2341                        } else {
2342                            ignore = true;
2343                            Some(line_nb + 1)
2344                        }
2345                    } else {
2346                        None
2347                    }
2348                }
2349            })
2350            .collect()
2351    }
2352
2353    /// This method is used for `//@ check-test-line-numbers-match`.
2354    ///
2355    /// It checks that doctests line in the displayed doctest "name" matches where they are
2356    /// defined in source code.
2357    fn check_rustdoc_test_option(&self, res: ProcRes) {
2358        let mut other_files = Vec::new();
2359        let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2360        let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2361        let normalized = normalized.to_str().unwrap().replace('\\', "/");
2362        files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2363        for other_file in other_files {
2364            let mut path = self.testpaths.file.clone();
2365            path.set_file_name(&format!("{}.rs", other_file));
2366            let path = path.canonicalize_utf8().expect("failed to canonicalize");
2367            let normalized = path.as_str().replace('\\', "/");
2368            files.insert(normalized, self.get_lines(&path, None));
2369        }
2370
2371        let mut tested = 0;
2372        for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2373            if let Some((left, right)) = s.split_once(" - ") {
2374                let path = left.rsplit("test ").next().unwrap();
2375                let path = fs::canonicalize(&path).expect("failed to canonicalize");
2376                let path = path.to_str().unwrap().replace('\\', "/");
2377                if let Some(ref mut v) = files.get_mut(&path) {
2378                    tested += 1;
2379                    let mut iter = right.split("(line ");
2380                    iter.next();
2381                    let line = iter
2382                        .next()
2383                        .unwrap_or(")")
2384                        .split(')')
2385                        .next()
2386                        .unwrap_or("0")
2387                        .parse()
2388                        .unwrap_or(0);
2389                    if let Ok(pos) = v.binary_search(&line) {
2390                        v.remove(pos);
2391                    } else {
2392                        self.fatal_proc_rec(
2393                            &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2394                            &res,
2395                        );
2396                    }
2397                }
2398            }
2399        }) {}
2400        if tested == 0 {
2401            self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2402        } else {
2403            for (entry, v) in &files {
2404                if !v.is_empty() {
2405                    self.fatal_proc_rec(
2406                        &format!(
2407                            "Not found test at line{} \"{}\":{:?}",
2408                            if v.len() > 1 { "s" } else { "" },
2409                            entry,
2410                            v
2411                        ),
2412                        &res,
2413                    );
2414                }
2415            }
2416        }
2417    }
2418
2419    fn force_color_svg(&self) -> bool {
2420        self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2421    }
2422
2423    fn load_compare_outputs(
2424        &self,
2425        proc_res: &ProcRes,
2426        output_kind: TestOutput,
2427        explicit_format: bool,
2428    ) -> usize {
2429        let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2430        let (stderr_kind, stdout_kind) = match output_kind {
2431            TestOutput::Compile => (
2432                if self.force_color_svg() {
2433                    if self.config.target.contains("windows") {
2434                        // We single out Windows here because some of the CLI coloring is
2435                        // specifically changed for Windows.
2436                        UI_WINDOWS_SVG
2437                    } else {
2438                        UI_SVG
2439                    }
2440                } else if self.props.stderr_per_bitwidth {
2441                    &stderr_bits
2442                } else {
2443                    UI_STDERR
2444                },
2445                UI_STDOUT,
2446            ),
2447            TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2448        };
2449
2450        let expected_stderr = self.load_expected_output(stderr_kind);
2451        let expected_stdout = self.load_expected_output(stdout_kind);
2452
2453        let mut normalized_stdout =
2454            self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2455        match output_kind {
2456            TestOutput::Run if self.config.remote_test_client.is_some() => {
2457                // When tests are run using the remote-test-client, the string
2458                // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"'
2459                // is printed to stdout by the client and then captured in the ProcRes,
2460                // so it needs to be removed when comparing the run-pass test execution output.
2461                normalized_stdout = static_regex!(
2462                    "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2463                )
2464                .replace(&normalized_stdout, "")
2465                .to_string();
2466                // When there is a panic, the remote-test-client also prints "died due to signal";
2467                // that needs to be removed as well.
2468                normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2469                    .replace(&normalized_stdout, "")
2470                    .to_string();
2471                // FIXME: it would be much nicer if we could just tell the remote-test-client to not
2472                // print these things.
2473            }
2474            _ => {}
2475        };
2476
2477        let stderr = if self.force_color_svg() {
2478            anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2479        } else if explicit_format {
2480            proc_res.stderr.clone()
2481        } else {
2482            json::extract_rendered(&proc_res.stderr)
2483        };
2484
2485        let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2486        let mut errors = 0;
2487        match output_kind {
2488            TestOutput::Compile => {
2489                if !self.props.dont_check_compiler_stdout {
2490                    if self
2491                        .compare_output(
2492                            stdout_kind,
2493                            &normalized_stdout,
2494                            &proc_res.stdout,
2495                            &expected_stdout,
2496                        )
2497                        .should_error()
2498                    {
2499                        errors += 1;
2500                    }
2501                }
2502                if !self.props.dont_check_compiler_stderr {
2503                    if self
2504                        .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2505                        .should_error()
2506                    {
2507                        errors += 1;
2508                    }
2509                }
2510            }
2511            TestOutput::Run => {
2512                if self
2513                    .compare_output(
2514                        stdout_kind,
2515                        &normalized_stdout,
2516                        &proc_res.stdout,
2517                        &expected_stdout,
2518                    )
2519                    .should_error()
2520                {
2521                    errors += 1;
2522                }
2523
2524                if self
2525                    .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2526                    .should_error()
2527                {
2528                    errors += 1;
2529                }
2530            }
2531        }
2532        errors
2533    }
2534
2535    fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2536        // Crude heuristic to detect when the output should have JSON-specific
2537        // normalization steps applied.
2538        let rflags = self.props.run_flags.join(" ");
2539        let cflags = self.props.compile_flags.join(" ");
2540        let json = rflags.contains("--format json")
2541            || rflags.contains("--format=json")
2542            || cflags.contains("--error-format json")
2543            || cflags.contains("--error-format pretty-json")
2544            || cflags.contains("--error-format=json")
2545            || cflags.contains("--error-format=pretty-json")
2546            || cflags.contains("--output-format json")
2547            || cflags.contains("--output-format=json");
2548
2549        let mut normalized = output.to_string();
2550
2551        let mut normalize_path = |from: &Utf8Path, to: &str| {
2552            let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
2553
2554            normalized = normalized.replace(from, to);
2555        };
2556
2557        let parent_dir = self.testpaths.file.parent().unwrap();
2558        normalize_path(parent_dir, "$DIR");
2559
2560        if self.props.remap_src_base {
2561            let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
2562            if self.testpaths.relative_dir != Utf8Path::new("") {
2563                remapped_parent_dir.push(&self.testpaths.relative_dir);
2564            }
2565            normalize_path(&remapped_parent_dir, "$DIR");
2566        }
2567
2568        let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
2569        // Fake paths into the libstd/libcore
2570        normalize_path(&base_dir.join("library"), "$SRC_DIR");
2571        // `ui-fulldeps` tests can show paths to the compiler source when testing macros from
2572        // `rustc_macros`
2573        // eg. /home/user/rust/compiler
2574        normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2575
2576        // Real paths into the libstd/libcore
2577        let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2578        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
2579        let rust_src_dir =
2580            rust_src_dir.read_link_utf8().unwrap_or_else(|_| rust_src_dir.to_path_buf());
2581        normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2582
2583        // Real paths into the compiler
2584        let rustc_src_dir = &self.config.sysroot_base.join("lib/rustlib/rustc-src/rust");
2585        rustc_src_dir.try_exists().expect(&*format!("{} should exists", rustc_src_dir));
2586        let rustc_src_dir = rustc_src_dir.read_link_utf8().unwrap_or(rustc_src_dir.to_path_buf());
2587        normalize_path(&rustc_src_dir.join("compiler"), "$COMPILER_DIR_REAL");
2588
2589        // eg.
2590        // /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
2591        normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2592        // Same as above, but with a canonicalized path.
2593        // This is required because some tests print canonical paths inside test build directory,
2594        // so if the build directory is a symlink, normalization doesn't help.
2595        //
2596        // NOTE: There are also tests which print the non-canonical name, so we need both this and
2597        // the above normalizations.
2598        normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
2599        // eg. /home/user/rust/build
2600        normalize_path(&self.config.build_root, "$BUILD_DIR");
2601
2602        if json {
2603            // escaped newlines in json strings should be readable
2604            // in the stderr files. There's no point in being correct,
2605            // since only humans process the stderr files.
2606            // Thus we just turn escaped newlines back into newlines.
2607            normalized = normalized.replace("\\n", "\n");
2608        }
2609
2610        // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them
2611        // with placeholders as we do not want tests needing updated when compiler source code
2612        // changes.
2613        // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL
2614        normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2615            .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2616            .into_owned();
2617
2618        normalized = Self::normalize_platform_differences(&normalized);
2619
2620        // Normalize long type name hash.
2621        normalized =
2622            static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2623                .replace_all(&normalized, |caps: &Captures<'_>| {
2624                    format!(
2625                        "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2626                        filename = &caps["filename"]
2627                    )
2628                })
2629                .into_owned();
2630
2631        // Normalize thread IDs in panic messages
2632        normalized = static_regex!(r"thread '(?P<name>.*?)' \((rtid )?\d+\) panicked")
2633            .replace_all(&normalized, "thread '$name' ($$TID) panicked")
2634            .into_owned();
2635
2636        normalized = normalized.replace("\t", "\\t"); // makes tabs visible
2637
2638        // Remove test annotations like `//~ ERROR text` from the output,
2639        // since they duplicate actual errors and make the output hard to read.
2640        // This mirrors the regex in src/tools/tidy/src/style.rs, please update
2641        // both if either are changed.
2642        normalized =
2643            static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2644
2645        // This code normalizes various hashes in v0 symbol mangling that is
2646        // emitted in the ui and mir-opt tests.
2647        let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2648        let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2649
2650        const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2651        if v0_crate_hash_prefix_re.is_match(&normalized) {
2652            // Normalize crate hash
2653            normalized =
2654                v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2655        }
2656
2657        let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2658        let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2659
2660        const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2661        if v0_back_ref_prefix_re.is_match(&normalized) {
2662            // Normalize back references (see RFC 2603)
2663            normalized =
2664                v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2665        }
2666
2667        // AllocId are numbered globally in a compilation session. This can lead to changes
2668        // depending on the exact compilation flags and host architecture. Meanwhile, we want
2669        // to keep them numbered, to see if the same id appears multiple times.
2670        // So we remap to deterministic numbers that only depend on the subset of allocations
2671        // that actually appear in the output.
2672        // We use uppercase ALLOC to distinguish from the non-normalized version.
2673        {
2674            let mut seen_allocs = indexmap::IndexSet::new();
2675
2676            // The alloc-id appears in pretty-printed allocations.
2677            normalized = static_regex!(
2678                r"╾─*a(lloc)?([0-9]+)(\+0x[0-9a-f]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2679            )
2680            .replace_all(&normalized, |caps: &Captures<'_>| {
2681                // Renumber the captured index.
2682                let index = caps.get(2).unwrap().as_str().to_string();
2683                let (index, _) = seen_allocs.insert_full(index);
2684                let offset = caps.get(3).map_or("", |c| c.as_str());
2685                let imm = caps.get(4).map_or("", |c| c.as_str());
2686                // Do not bother keeping it pretty, just make it deterministic.
2687                format!("╾ALLOC{index}{offset}{imm}╼")
2688            })
2689            .into_owned();
2690
2691            // The alloc-id appears in a sentence.
2692            normalized = static_regex!(r"\balloc([0-9]+)\b")
2693                .replace_all(&normalized, |caps: &Captures<'_>| {
2694                    let index = caps.get(1).unwrap().as_str().to_string();
2695                    let (index, _) = seen_allocs.insert_full(index);
2696                    format!("ALLOC{index}")
2697                })
2698                .into_owned();
2699        }
2700
2701        // Custom normalization rules
2702        for rule in custom_rules {
2703            let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2704            normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2705        }
2706        normalized
2707    }
2708
2709    /// Normalize output differences across platforms. Generally changes Windows output to be more
2710    /// Unix-like.
2711    ///
2712    /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings
2713    /// with LF.
2714    fn normalize_platform_differences(output: &str) -> String {
2715        let output = output.replace(r"\\", r"\");
2716
2717        // Used to find Windows paths.
2718        //
2719        // It's not possible to detect paths in the error messages generally, but this is a
2720        // decent enough heuristic.
2721        let re = static_regex!(
2722            r#"(?x)
2723                (?:
2724                  # Match paths that don't include spaces.
2725                  (?:\\[\pL\pN\.\-_']+)+\.\pL+
2726                |
2727                  # If the path starts with a well-known root, then allow spaces and no file extension.
2728                  \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2729                )"#
2730        );
2731        re.replace_all(&output, |caps: &Captures<'_>| caps[0].replace(r"\", "/"))
2732            .replace("\r\n", "\n")
2733    }
2734
2735    fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
2736        let mut path =
2737            expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2738
2739        if !path.exists() {
2740            if let Some(CompareMode::Polonius) = self.config.compare_mode {
2741                path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2742            }
2743        }
2744
2745        if !path.exists() {
2746            path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2747        }
2748
2749        path
2750    }
2751
2752    fn load_expected_output(&self, kind: &str) -> String {
2753        let path = self.expected_output_path(kind);
2754        if path.exists() {
2755            match self.load_expected_output_from_path(&path) {
2756                Ok(x) => x,
2757                Err(x) => self.fatal(&x),
2758            }
2759        } else {
2760            String::new()
2761        }
2762    }
2763
2764    fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
2765        fs::read_to_string(path)
2766            .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
2767    }
2768
2769    fn delete_file(&self, file: &Utf8Path) {
2770        if !file.exists() {
2771            // Deleting a nonexistent file would error.
2772            return;
2773        }
2774        if let Err(e) = fs::remove_file(file.as_std_path()) {
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            // FIXME: We ignore the first line of SVG files
2795            // because the width parameter is non-deterministic.
2796            (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        // Wrapper tools set by `runner` might provide extra output on failure,
2804        // for example a WebAssembly runtime might print the stack trace of an
2805        // `unreachable` instruction by default.
2806        //
2807        // Also, some tests like `ui/parallel-rustc` have non-deterministic
2808        // orders of output, so we need to compare by lines.
2809        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            // check if `expected` contains a subset of the lines of `actual`
2819            if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2820                return CompareOutcome::Same;
2821            }
2822            if expected_lines.is_empty() {
2823                // if we have no lines to check, force a full overwite
2824                ("", 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        // Write the actual output to a file in build directory.
2834        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            // Delete non-revision .stderr/.stdout file if revisions are used.
2862            // Without this, we'd just generate the new files and leave the old files around.
2863            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    /// Returns whether to show the full stderr/stdout.
2887    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        // NOTE: argument order is important, we need `actual` to be on the left so the line number match up when we compare it to `actual_unnormalized` below.
2916        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                // NOTE: `Expected` is actually correct here, the argument order is reversed so our line numbers match up
2923                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        // HACK: instead of checking if each hunk is empty, this only checks if the whole input is empty. we should be smarter about this so we don't treat added or removed output as normalized.
2946        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            // FIXME: respect diff_command
2955            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                // If there is no output, there is nothing to do
2983                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                    // If we're blessing and the output is the same, then delete the file.
2992                    (true, _, true) => {
2993                        self.delete_file(&examined_path);
2994                    }
2995                    // If we want them to be the same, but they are different, then error.
2996                    // We do this whether we bless or not
2997                    (_, 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        // (See `run_incremental_test` for an overview of how incremental tests work.)
3022
3023        // Before any of the revisions have executed, create the
3024        // incremental workproduct directory.  Delete any old
3025        // incremental work products that may be there from prior
3026        // runs.
3027        let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
3028        if incremental_dir.exists() {
3029            // Canonicalizing the path will convert it to the //?/ format
3030            // on Windows, which enables paths longer than 260 character
3031            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/// Outcome of comparing a stream to a blessed file,
3109/// e.g. `.stderr` and `.fixed`.
3110#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3111enum CompareOutcome {
3112    /// Expected and actual outputs are the same
3113    Same,
3114    /// Outputs differed but were blessed
3115    Blessed,
3116    /// Outputs differed and an error should be emitted
3117    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}