compiletest/
runtest.rs

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