compiletest/
runtest.rs

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