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.host_compile_lib_path.as_path(),
462            Some(aux_dir.as_path()),
463            src,
464        )
465    }
466
467    fn compare_source(&self, expected: &str, actual: &str) {
468        if expected != actual {
469            self.fatal(&format!(
470                "pretty-printed source does not match expected source\n\
471                 expected:\n\
472                 ------------------------------------------\n\
473                 {}\n\
474                 ------------------------------------------\n\
475                 actual:\n\
476                 ------------------------------------------\n\
477                 {}\n\
478                 ------------------------------------------\n\
479                 diff:\n\
480                 ------------------------------------------\n\
481                 {}\n",
482                expected,
483                actual,
484                write_diff(expected, actual, 3),
485            ));
486        }
487    }
488
489    fn set_revision_flags(&self, cmd: &mut Command) {
490        // 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.target_run_lib_path.as_path())
1064            .arg("-L")
1065            .arg(aux_dir)
1066            .arg("-o")
1067            .arg(out_dir.as_ref())
1068            .arg("--deny")
1069            .arg("warnings")
1070            .arg(file_to_doc)
1071            .arg("-A")
1072            .arg("internal_features")
1073            .args(&self.props.compile_flags)
1074            .args(&self.props.doc_flags);
1075
1076        match kind {
1077            DocKind::Html => {}
1078            DocKind::Json => {
1079                rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options");
1080            }
1081        }
1082
1083        if let Some(ref linker) = self.config.target_linker {
1084            rustdoc.arg(format!("-Clinker={}", linker));
1085        }
1086
1087        self.compose_and_run_compiler(rustdoc, None)
1088    }
1089
1090    fn exec_compiled_test(&self) -> ProcRes {
1091        self.exec_compiled_test_general(&[], true)
1092    }
1093
1094    fn exec_compiled_test_general(
1095        &self,
1096        env_extra: &[(&str, &str)],
1097        delete_after_success: bool,
1098    ) -> ProcRes {
1099        let prepare_env = |cmd: &mut Command| {
1100            for (key, val) in &self.props.exec_env {
1101                cmd.env(key, val);
1102            }
1103            for (key, val) in env_extra {
1104                cmd.env(key, val);
1105            }
1106
1107            for key in &self.props.unset_exec_env {
1108                cmd.env_remove(key);
1109            }
1110        };
1111
1112        let proc_res = match &*self.config.target {
1113            // 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.target_run_lib_path.as_path(),
1155                    Some(aux_dir.as_path()),
1156                    None,
1157                )
1158            }
1159            _ if self.config.target.contains("vxworks") => {
1160                let aux_dir = self.aux_output_dir_name();
1161                let ProcArgs { prog, args } = self.make_run_args();
1162                let mut wr_run = Command::new("wr-run");
1163                wr_run.args(&[&prog]).args(args);
1164
1165                prepare_env(&mut wr_run);
1166
1167                self.compose_and_run(
1168                    wr_run,
1169                    self.config.target_run_lib_path.as_path(),
1170                    Some(aux_dir.as_path()),
1171                    None,
1172                )
1173            }
1174            _ => {
1175                let aux_dir = self.aux_output_dir_name();
1176                let ProcArgs { prog, args } = self.make_run_args();
1177                let mut program = Command::new(&prog);
1178                program.args(args).current_dir(&self.output_base_dir());
1179
1180                prepare_env(&mut program);
1181
1182                self.compose_and_run(
1183                    program,
1184                    self.config.target_run_lib_path.as_path(),
1185                    Some(aux_dir.as_path()),
1186                    None,
1187                )
1188            }
1189        };
1190
1191        if delete_after_success && proc_res.status.success() {
1192            // 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.host_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 =
1348            self.compose_and_run(rustc, self.config.host_compile_lib_path.as_path(), None, None);
1349        if !res.status.success() {
1350            self.fatal_proc_rec(
1351                &format!("auxiliary build of {} failed to compile: ", self.config.minicore_path),
1352                &res,
1353            );
1354        }
1355
1356        output_file_path
1357    }
1358
1359    /// Builds an aux dependency.
1360    ///
1361    /// If `aux_type` is `None`, then this will determine the aux-type automatically.
1362    fn build_auxiliary(
1363        &self,
1364        source_path: &str,
1365        aux_dir: &Utf8Path,
1366        aux_type: Option<AuxType>,
1367    ) -> AuxType {
1368        let aux_path = self.resolve_aux_path(source_path);
1369        let mut aux_props = self.props.from_aux_file(&aux_path, self.revision, self.config);
1370        if aux_type == Some(AuxType::ProcMacro) {
1371            aux_props.force_host = true;
1372        }
1373        let mut aux_dir = aux_dir.to_path_buf();
1374        if aux_type == Some(AuxType::Bin) {
1375            // On unix, the binary of `auxiliary/foo.rs` will be named
1376            // `auxiliary/foo` which clashes with the _dir_ `auxiliary/foo`, so
1377            // put bins in a `bin` subfolder.
1378            aux_dir.push("bin");
1379        }
1380        let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1381        let aux_cx = TestCx {
1382            config: self.config,
1383            stdout: self.stdout,
1384            stderr: self.stderr,
1385            props: &aux_props,
1386            testpaths: self.testpaths,
1387            revision: self.revision,
1388        };
1389        // Create the directory for the stdout/stderr files.
1390        create_dir_all(aux_cx.output_base_dir()).unwrap();
1391        let mut aux_rustc = aux_cx.make_compile_args(
1392            &aux_path,
1393            aux_output,
1394            Emit::None,
1395            AllowUnused::No,
1396            LinkToAux::No,
1397            Vec::new(),
1398        );
1399        aux_cx.build_all_auxiliary(&aux_dir, &mut aux_rustc);
1400
1401        aux_rustc.envs(aux_props.rustc_env.clone());
1402        for key in &aux_props.unset_rustc_env {
1403            aux_rustc.env_remove(key);
1404        }
1405
1406        let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1407            (AuxType::Bin, Some("bin"))
1408        } else if aux_type == Some(AuxType::ProcMacro) {
1409            (AuxType::ProcMacro, Some("proc-macro"))
1410        } else if aux_type.is_some() {
1411            panic!("aux_type {aux_type:?} not expected");
1412        } else if aux_props.no_prefer_dynamic {
1413            (AuxType::Dylib, None)
1414        } else if self.config.target.contains("emscripten")
1415            || (self.config.target.contains("musl")
1416                && !aux_props.force_host
1417                && !self.config.host.contains("musl"))
1418            || self.config.target.contains("wasm32")
1419            || self.config.target.contains("nvptx")
1420            || self.is_vxworks_pure_static()
1421            || self.config.target.contains("bpf")
1422            || !self.config.target_cfg().dynamic_linking
1423            || matches!(self.config.mode, TestMode::CoverageMap | TestMode::CoverageRun)
1424        {
1425            // We primarily compile all auxiliary libraries as dynamic libraries
1426            // to avoid code size bloat and large binaries as much as possible
1427            // for the test suite (otherwise including libstd statically in all
1428            // executables takes up quite a bit of space).
1429            //
1430            // For targets like MUSL or Emscripten, however, there is no support for
1431            // dynamic libraries so we just go back to building a normal library. Note,
1432            // however, that for MUSL if the library is built with `force_host` then
1433            // it's ok to be a dylib as the host should always support dylibs.
1434            //
1435            // Coverage tests want static linking by default so that coverage
1436            // mappings in auxiliary libraries can be merged into the final
1437            // executable.
1438            (AuxType::Lib, Some("lib"))
1439        } else {
1440            (AuxType::Dylib, Some("dylib"))
1441        };
1442
1443        if let Some(crate_type) = crate_type {
1444            aux_rustc.args(&["--crate-type", crate_type]);
1445        }
1446
1447        if aux_type == AuxType::ProcMacro {
1448            // For convenience, but this only works on 2018.
1449            aux_rustc.args(&["--extern", "proc_macro"]);
1450        }
1451
1452        aux_rustc.arg("-L").arg(&aux_dir);
1453
1454        if aux_props.add_minicore {
1455            let minicore_path = self.build_minicore();
1456            aux_rustc.arg("--extern");
1457            aux_rustc.arg(&format!("minicore={}", minicore_path));
1458        }
1459
1460        let auxres = aux_cx.compose_and_run(
1461            aux_rustc,
1462            aux_cx.config.host_compile_lib_path.as_path(),
1463            Some(aux_dir.as_path()),
1464            None,
1465        );
1466        if !auxres.status.success() {
1467            self.fatal_proc_rec(
1468                &format!("auxiliary build of {aux_path} failed to compile: "),
1469                &auxres,
1470            );
1471        }
1472        aux_type
1473    }
1474
1475    fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1476        let mut filter_paths_from_len = Vec::new();
1477        let mut add_path = |path: &Utf8Path| {
1478            let path = path.to_string();
1479            let windows = path.replace("\\", "\\\\");
1480            if windows != path {
1481                filter_paths_from_len.push(windows);
1482            }
1483            filter_paths_from_len.push(path);
1484        };
1485
1486        // List of paths that will not be measured when determining whether the output is larger
1487        // than the output truncation threshold.
1488        //
1489        // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the
1490        // same slice of text will be double counted and the truncation might not happen.
1491        add_path(&self.config.src_test_suite_root);
1492        add_path(&self.config.build_test_suite_root);
1493
1494        read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1495    }
1496
1497    fn compose_and_run(
1498        &self,
1499        mut command: Command,
1500        lib_path: &Utf8Path,
1501        aux_path: Option<&Utf8Path>,
1502        input: Option<String>,
1503    ) -> ProcRes {
1504        let cmdline = {
1505            let cmdline = self.make_cmdline(&command, lib_path);
1506            self.logv(format_args!("executing {cmdline}"));
1507            cmdline
1508        };
1509
1510        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1511
1512        // Need to be sure to put both the lib_path and the aux path in the dylib
1513        // search path for the child.
1514        add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1515
1516        let mut child = disable_error_reporting(|| command.spawn())
1517            .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1518        if let Some(input) = input {
1519            child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1520        }
1521
1522        let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1523
1524        let result = ProcRes {
1525            status,
1526            stdout: String::from_utf8_lossy(&stdout).into_owned(),
1527            stderr: String::from_utf8_lossy(&stderr).into_owned(),
1528            truncated,
1529            cmdline,
1530        };
1531
1532        self.dump_output(
1533            self.config.verbose || (!result.status.success() && self.config.mode != TestMode::Ui),
1534            &command.get_program().to_string_lossy(),
1535            &result.stdout,
1536            &result.stderr,
1537        );
1538
1539        result
1540    }
1541
1542    fn is_rustdoc(&self) -> bool {
1543        matches!(
1544            self.config.suite,
1545            TestSuite::RustdocUi | TestSuite::RustdocJs | TestSuite::RustdocJson
1546        )
1547    }
1548
1549    fn make_compile_args(
1550        &self,
1551        input_file: &Utf8Path,
1552        output_file: TargetLocation,
1553        emit: Emit,
1554        allow_unused: AllowUnused,
1555        link_to_aux: LinkToAux,
1556        passes: Vec<String>, // Vec of passes under mir-opt test to be dumped
1557    ) -> Command {
1558        let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary");
1559        let is_rustdoc = self.is_rustdoc() && !is_aux;
1560        let mut rustc = if !is_rustdoc {
1561            Command::new(&self.config.rustc_path)
1562        } else {
1563            Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1564        };
1565        rustc.arg(input_file);
1566
1567        // Use a single thread for efficiency and a deterministic error message order
1568        rustc.arg("-Zthreads=1");
1569
1570        // Hide libstd sources from ui tests to make sure we generate the stderr
1571        // output that users will see.
1572        // Without this, we may be producing good diagnostics in-tree but users
1573        // will not see half the information.
1574        //
1575        // This also has the benefit of more effectively normalizing output between different
1576        // compilers, so that we don't have to know the `/rustc/$sha` output to normalize after the
1577        // fact.
1578        rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1579        rustc.arg("-Ztranslate-remapped-path-to-local-path=no");
1580
1581        // Hide Cargo dependency sources from ui tests to make sure the error message doesn't
1582        // change depending on whether $CARGO_HOME is remapped or not. If this is not present,
1583        // when $CARGO_HOME is remapped the source won't be shown, and when it's not remapped the
1584        // source will be shown, causing a blessing hell.
1585        rustc.arg("-Z").arg(format!(
1586            "ignore-directory-in-diagnostics-source-blocks={}",
1587            home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1588        ));
1589        // Similarly, vendored sources shouldn't be shown when running from a dist tarball.
1590        rustc.arg("-Z").arg(format!(
1591            "ignore-directory-in-diagnostics-source-blocks={}",
1592            self.config.src_root.join("vendor"),
1593        ));
1594
1595        // Optionally prevent default --sysroot if specified in test compile-flags.
1596        //
1597        // FIXME: I feel like this logic is fairly sus.
1598        if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1599            && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1600        {
1601            // In stage 0, make sure we use `stage0-sysroot` instead of the bootstrap sysroot.
1602            rustc.arg("--sysroot").arg(&self.config.sysroot_base);
1603        }
1604
1605        // If the provided codegen backend is not LLVM, we need to pass it.
1606        if let Some(ref backend) = self.config.override_codegen_backend {
1607            rustc.arg(format!("-Zcodegen-backend={}", backend));
1608        }
1609
1610        // Optionally prevent default --target if specified in test compile-flags.
1611        let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1612
1613        if !custom_target {
1614            let target =
1615                if self.props.force_host { &*self.config.host } else { &*self.config.target };
1616
1617            rustc.arg(&format!("--target={}", target));
1618        }
1619        self.set_revision_flags(&mut rustc);
1620
1621        if !is_rustdoc {
1622            if let Some(ref incremental_dir) = self.props.incremental_dir {
1623                rustc.args(&["-C", &format!("incremental={}", incremental_dir)]);
1624                rustc.args(&["-Z", "incremental-verify-ich"]);
1625            }
1626
1627            if self.config.mode == TestMode::CodegenUnits {
1628                rustc.args(&["-Z", "human_readable_cgu_names"]);
1629            }
1630        }
1631
1632        if self.config.optimize_tests && !is_rustdoc {
1633            match self.config.mode {
1634                TestMode::Ui => {
1635                    // If optimize-tests is true we still only want to optimize tests that actually get
1636                    // executed and that don't specify their own optimization levels.
1637                    // Note: aux libs don't have a pass-mode, so they won't get optimized
1638                    // unless compile-flags are set in the aux file.
1639                    if self.props.pass_mode(&self.config) == Some(PassMode::Run)
1640                        && !self
1641                            .props
1642                            .compile_flags
1643                            .iter()
1644                            .any(|arg| arg == "-O" || arg.contains("opt-level"))
1645                    {
1646                        rustc.arg("-O");
1647                    }
1648                }
1649                TestMode::DebugInfo => { /* debuginfo tests must be unoptimized */ }
1650                TestMode::CoverageMap | TestMode::CoverageRun => {
1651                    // Coverage mappings and coverage reports are affected by
1652                    // optimization level, so they ignore the optimize-tests
1653                    // setting and set an optimization level in their mode's
1654                    // compile flags (below) or in per-test `compile-flags`.
1655                }
1656                _ => {
1657                    rustc.arg("-O");
1658                }
1659            }
1660        }
1661
1662        let set_mir_dump_dir = |rustc: &mut Command| {
1663            let mir_dump_dir = self.output_base_dir();
1664            let mut dir_opt = "-Zdump-mir-dir=".to_string();
1665            dir_opt.push_str(mir_dump_dir.as_str());
1666            debug!("dir_opt: {:?}", dir_opt);
1667            rustc.arg(dir_opt);
1668        };
1669
1670        match self.config.mode {
1671            TestMode::Incremental => {
1672                // If we are extracting and matching errors in the new
1673                // fashion, then you want JSON mode. Old-skool error
1674                // patterns still match the raw compiler output.
1675                if self.props.error_patterns.is_empty()
1676                    && self.props.regex_error_patterns.is_empty()
1677                {
1678                    rustc.args(&["--error-format", "json"]);
1679                    rustc.args(&["--json", "future-incompat"]);
1680                }
1681                rustc.arg("-Zui-testing");
1682                rustc.arg("-Zdeduplicate-diagnostics=no");
1683            }
1684            TestMode::Ui => {
1685                if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1686                    rustc.args(&["--error-format", "json"]);
1687                    rustc.args(&["--json", "future-incompat"]);
1688                }
1689                rustc.arg("-Ccodegen-units=1");
1690                // Hide line numbers to reduce churn
1691                rustc.arg("-Zui-testing");
1692                rustc.arg("-Zdeduplicate-diagnostics=no");
1693                rustc.arg("-Zwrite-long-types-to-disk=no");
1694                // FIXME: use this for other modes too, for perf?
1695                rustc.arg("-Cstrip=debuginfo");
1696            }
1697            TestMode::MirOpt => {
1698                // We check passes under test to minimize the mir-opt test dump
1699                // if files_for_miropt_test parses the passes, we dump only those passes
1700                // otherwise we conservatively pass -Zdump-mir=all
1701                let zdump_arg = if !passes.is_empty() {
1702                    format!("-Zdump-mir={}", passes.join(" | "))
1703                } else {
1704                    "-Zdump-mir=all".to_string()
1705                };
1706
1707                rustc.args(&[
1708                    "-Copt-level=1",
1709                    &zdump_arg,
1710                    "-Zvalidate-mir",
1711                    "-Zlint-mir",
1712                    "-Zdump-mir-exclude-pass-number",
1713                    "-Zmir-include-spans=false", // remove span comments from NLL MIR dumps
1714                    "--crate-type=rlib",
1715                ]);
1716                if let Some(pass) = &self.props.mir_unit_test {
1717                    rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1718                } else {
1719                    rustc.args(&[
1720                        "-Zmir-opt-level=4",
1721                        "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1722                    ]);
1723                }
1724
1725                set_mir_dump_dir(&mut rustc);
1726            }
1727            TestMode::CoverageMap => {
1728                rustc.arg("-Cinstrument-coverage");
1729                // These tests only compile to LLVM IR, so they don't need the
1730                // profiler runtime to be present.
1731                rustc.arg("-Zno-profiler-runtime");
1732                // Coverage mappings are sensitive to MIR optimizations, and
1733                // the current snapshots assume `opt-level=2` unless overridden
1734                // by `compile-flags`.
1735                rustc.arg("-Copt-level=2");
1736            }
1737            TestMode::CoverageRun => {
1738                rustc.arg("-Cinstrument-coverage");
1739                // Coverage reports are sometimes sensitive to optimizations,
1740                // and the current snapshots assume `opt-level=2` unless
1741                // overridden by `compile-flags`.
1742                rustc.arg("-Copt-level=2");
1743            }
1744            TestMode::Assembly | TestMode::Codegen => {
1745                rustc.arg("-Cdebug-assertions=no");
1746                // For assembly and codegen tests, we want to use the same order
1747                // of the items of a codegen unit as the source order, so that
1748                // we can compare the output with the source code through filecheck.
1749                rustc.arg("-Zcodegen-source-order");
1750            }
1751            TestMode::Crashes => {
1752                set_mir_dump_dir(&mut rustc);
1753            }
1754            TestMode::CodegenUnits => {
1755                rustc.arg("-Zprint-mono-items");
1756            }
1757            TestMode::Pretty
1758            | TestMode::DebugInfo
1759            | TestMode::Rustdoc
1760            | TestMode::RustdocJson
1761            | TestMode::RunMake
1762            | TestMode::RustdocJs => {
1763                // do not use JSON output
1764            }
1765        }
1766
1767        if self.props.remap_src_base {
1768            rustc.arg(format!(
1769                "--remap-path-prefix={}={}",
1770                self.config.src_test_suite_root, FAKE_SRC_BASE,
1771            ));
1772        }
1773
1774        match emit {
1775            Emit::None => {}
1776            Emit::Metadata if is_rustdoc => {}
1777            Emit::Metadata => {
1778                rustc.args(&["--emit", "metadata"]);
1779            }
1780            Emit::LlvmIr => {
1781                rustc.args(&["--emit", "llvm-ir"]);
1782            }
1783            Emit::Mir => {
1784                rustc.args(&["--emit", "mir"]);
1785            }
1786            Emit::Asm => {
1787                rustc.args(&["--emit", "asm"]);
1788            }
1789            Emit::LinkArgsAsm => {
1790                rustc.args(&["-Clink-args=--emit=asm"]);
1791            }
1792        }
1793
1794        if !is_rustdoc {
1795            if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1796                // rustc.arg("-g"); // get any backtrace at all on errors
1797            } else if !self.props.no_prefer_dynamic {
1798                rustc.args(&["-C", "prefer-dynamic"]);
1799            }
1800        }
1801
1802        match output_file {
1803            // If the test's compile flags specify an output path with `-o`,
1804            // avoid a compiler warning about `--out-dir` being ignored.
1805            _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1806            TargetLocation::ThisFile(path) => {
1807                rustc.arg("-o").arg(path);
1808            }
1809            TargetLocation::ThisDirectory(path) => {
1810                if is_rustdoc {
1811                    // `rustdoc` uses `-o` for the output directory.
1812                    rustc.arg("-o").arg(path);
1813                } else {
1814                    rustc.arg("--out-dir").arg(path);
1815                }
1816            }
1817        }
1818
1819        match self.config.compare_mode {
1820            Some(CompareMode::Polonius) => {
1821                rustc.args(&["-Zpolonius=next"]);
1822            }
1823            Some(CompareMode::NextSolver) => {
1824                rustc.args(&["-Znext-solver"]);
1825            }
1826            Some(CompareMode::NextSolverCoherence) => {
1827                rustc.args(&["-Znext-solver=coherence"]);
1828            }
1829            Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1830                rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1831            }
1832            Some(CompareMode::SplitDwarf) => {
1833                rustc.args(&["-Csplit-debuginfo=unpacked"]);
1834            }
1835            Some(CompareMode::SplitDwarfSingle) => {
1836                rustc.args(&["-Csplit-debuginfo=packed"]);
1837            }
1838            None => {}
1839        }
1840
1841        // Add `-A unused` before `config` flags and in-test (`props`) flags, so that they can
1842        // overwrite this.
1843        // Don't allow `unused_attributes` since these are usually actual mistakes, rather than just unused code.
1844        if let AllowUnused::Yes = allow_unused {
1845            rustc.args(&["-A", "unused", "-W", "unused_attributes"]);
1846        }
1847
1848        // Allow tests to use internal features.
1849        rustc.args(&["-A", "internal_features"]);
1850
1851        // Allow tests to have unused parens and braces.
1852        // Add #![deny(unused_parens, unused_braces)] to the test file if you want to
1853        // test that these lints are working.
1854        rustc.args(&["-A", "unused_parens"]);
1855        rustc.args(&["-A", "unused_braces"]);
1856
1857        if self.props.force_host {
1858            self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1859            if !is_rustdoc {
1860                if let Some(ref linker) = self.config.host_linker {
1861                    rustc.arg(format!("-Clinker={}", linker));
1862                }
1863            }
1864        } else {
1865            self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1866            if !is_rustdoc {
1867                if let Some(ref linker) = self.config.target_linker {
1868                    rustc.arg(format!("-Clinker={}", linker));
1869                }
1870            }
1871        }
1872
1873        // Use dynamic musl for tests because static doesn't allow creating dylibs
1874        if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1875            rustc.arg("-Ctarget-feature=-crt-static");
1876        }
1877
1878        if let LinkToAux::Yes = link_to_aux {
1879            // if we pass an `-L` argument to a directory that doesn't exist,
1880            // macOS ld emits warnings which disrupt the .stderr files
1881            if self.has_aux_dir() {
1882                rustc.arg("-L").arg(self.aux_output_dir_name());
1883            }
1884        }
1885
1886        // FIXME(jieyouxu): we should report a fatal error or warning if user wrote `-Cpanic=` with
1887        // something that's not `abort` and `-Cforce-unwind-tables` with a value that is not `yes`.
1888        //
1889        // We could apply these last and override any provided flags. That would ensure that the
1890        // build works, but some tests want to exercise that mixing panic modes in specific ways is
1891        // rejected. So we enable aborting panics and unwind tables before adding flags, just to
1892        // change the default.
1893        //
1894        // `minicore` requires `#![no_std]` and `#![no_core]`, which means no unwinding panics.
1895        if self.props.add_minicore {
1896            rustc.arg("-Cpanic=abort");
1897            rustc.arg("-Cforce-unwind-tables=yes");
1898        }
1899
1900        rustc.args(&self.props.compile_flags);
1901
1902        rustc
1903    }
1904
1905    fn make_exe_name(&self) -> Utf8PathBuf {
1906        // Using a single letter here to keep the path length down for
1907        // Windows.  Some test names get very long.  rustc creates `rcgu`
1908        // files with the module name appended to it which can more than
1909        // double the length.
1910        let mut f = self.output_base_dir().join("a");
1911        // FIXME: This is using the host architecture exe suffix, not target!
1912        if self.config.target.contains("emscripten") {
1913            f = f.with_extra_extension("js");
1914        } else if self.config.target.starts_with("wasm") {
1915            f = f.with_extra_extension("wasm");
1916        } else if self.config.target.contains("spirv") {
1917            f = f.with_extra_extension("spv");
1918        } else if !env::consts::EXE_SUFFIX.is_empty() {
1919            f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1920        }
1921        f
1922    }
1923
1924    fn make_run_args(&self) -> ProcArgs {
1925        // If we've got another tool to run under (valgrind),
1926        // then split apart its command
1927        let mut args = self.split_maybe_args(&self.config.runner);
1928
1929        let exe_file = self.make_exe_name();
1930
1931        args.push(exe_file.into_os_string());
1932
1933        // Add the arguments in the run_flags directive
1934        args.extend(self.props.run_flags.iter().map(OsString::from));
1935
1936        let prog = args.remove(0);
1937        ProcArgs { prog, args }
1938    }
1939
1940    fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1941        match *argstr {
1942            Some(ref s) => s
1943                .split(' ')
1944                .filter_map(|s| {
1945                    if s.chars().all(|c| c.is_whitespace()) {
1946                        None
1947                    } else {
1948                        Some(OsString::from(s))
1949                    }
1950                })
1951                .collect(),
1952            None => Vec::new(),
1953        }
1954    }
1955
1956    fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
1957        use crate::util;
1958
1959        // Linux and mac don't require adjusting the library search path
1960        if cfg!(unix) {
1961            format!("{:?}", command)
1962        } else {
1963            // Build the LD_LIBRARY_PATH variable as it would be seen on the command line
1964            // for diagnostic purposes
1965            fn lib_path_cmd_prefix(path: &str) -> String {
1966                format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1967            }
1968
1969            format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
1970        }
1971    }
1972
1973    fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1974        let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1975
1976        self.dump_output_file(out, &format!("{}out", revision));
1977        self.dump_output_file(err, &format!("{}err", revision));
1978
1979        if !print_output {
1980            return;
1981        }
1982
1983        let path = Utf8Path::new(proc_name);
1984        let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1985            String::from_iter(
1986                path.parent()
1987                    .unwrap()
1988                    .file_name()
1989                    .into_iter()
1990                    .chain(Some("/"))
1991                    .chain(path.file_name()),
1992            )
1993        } else {
1994            path.file_name().unwrap().into()
1995        };
1996        writeln!(self.stdout, "------{proc_name} stdout------------------------------");
1997        writeln!(self.stdout, "{}", out);
1998        writeln!(self.stdout, "------{proc_name} stderr------------------------------");
1999        writeln!(self.stdout, "{}", err);
2000        writeln!(self.stdout, "------------------------------------------");
2001    }
2002
2003    fn dump_output_file(&self, out: &str, extension: &str) {
2004        let outfile = self.make_out_name(extension);
2005        fs::write(outfile.as_std_path(), out)
2006            .unwrap_or_else(|err| panic!("failed to write {outfile}: {err:?}"));
2007    }
2008
2009    /// Creates a filename for output with the given extension.
2010    /// E.g., `/.../testname.revision.mode/testname.extension`.
2011    fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
2012        self.output_base_name().with_extension(extension)
2013    }
2014
2015    /// Gets the directory where auxiliary files are written.
2016    /// E.g., `/.../testname.revision.mode/auxiliary/`.
2017    fn aux_output_dir_name(&self) -> Utf8PathBuf {
2018        self.output_base_dir()
2019            .join("auxiliary")
2020            .with_extra_extension(self.config.mode.aux_dir_disambiguator())
2021    }
2022
2023    /// Gets the directory where auxiliary binaries are written.
2024    /// E.g., `/.../testname.revision.mode/auxiliary/bin`.
2025    fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
2026        self.aux_output_dir_name().join("bin")
2027    }
2028
2029    /// The revision, ignored for incremental compilation since it wants all revisions in
2030    /// the same directory.
2031    fn safe_revision(&self) -> Option<&str> {
2032        if self.config.mode == TestMode::Incremental { None } else { self.revision }
2033    }
2034
2035    /// Gets the absolute path to the directory where all output for the given
2036    /// test/revision should reside.
2037    /// E.g., `/path/to/build/host-tuple/test/ui/relative/testname.revision.mode/`.
2038    fn output_base_dir(&self) -> Utf8PathBuf {
2039        output_base_dir(self.config, self.testpaths, self.safe_revision())
2040    }
2041
2042    /// Gets the absolute path to the base filename used as output for the given
2043    /// test/revision.
2044    /// E.g., `/.../relative/testname.revision.mode/testname`.
2045    fn output_base_name(&self) -> Utf8PathBuf {
2046        output_base_name(self.config, self.testpaths, self.safe_revision())
2047    }
2048
2049    /// Prints a message to (captured) stdout if `config.verbose` is true.
2050    /// The message is also logged to `tracing::debug!` regardles of verbosity.
2051    ///
2052    /// Use `format_args!` as the argument to perform formatting if required.
2053    fn logv(&self, message: impl fmt::Display) {
2054        debug!("{message}");
2055        if self.config.verbose {
2056            // Note: `./x test ... --verbose --no-capture` is needed to see this print.
2057            writeln!(self.stdout, "{message}");
2058        }
2059    }
2060
2061    /// Prefix to print before error messages. Normally just `error`, but also
2062    /// includes the revision name for tests that use revisions.
2063    #[must_use]
2064    fn error_prefix(&self) -> String {
2065        match self.revision {
2066            Some(rev) => format!("error in revision `{rev}`"),
2067            None => format!("error"),
2068        }
2069    }
2070
2071    #[track_caller]
2072    fn fatal(&self, err: &str) -> ! {
2073        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2074        error!("fatal error, panic: {:?}", err);
2075        panic!("fatal error");
2076    }
2077
2078    fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
2079        self.fatal_proc_rec_general(err, None, proc_res, || ());
2080    }
2081
2082    /// Underlying implementation of [`Self::fatal_proc_rec`], providing some
2083    /// extra capabilities not needed by most callers.
2084    fn fatal_proc_rec_general(
2085        &self,
2086        err: &str,
2087        extra_note: Option<&str>,
2088        proc_res: &ProcRes,
2089        callback_before_unwind: impl FnOnce(),
2090    ) -> ! {
2091        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2092
2093        // Some callers want to print additional notes after the main error message.
2094        if let Some(note) = extra_note {
2095            writeln!(self.stdout, "{note}");
2096        }
2097
2098        // Print the details and output of the subprocess that caused this test to fail.
2099        writeln!(self.stdout, "{}", proc_res.format_info());
2100
2101        // Some callers want print more context or show a custom diff before the unwind occurs.
2102        callback_before_unwind();
2103
2104        // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from
2105        // compiletest, which is unnecessary noise.
2106        std::panic::resume_unwind(Box::new(()));
2107    }
2108
2109    // codegen tests (using FileCheck)
2110
2111    fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
2112        let output_path = self.output_base_name().with_extension("ll");
2113        let input_file = &self.testpaths.file;
2114        let rustc = self.make_compile_args(
2115            input_file,
2116            TargetLocation::ThisFile(output_path.clone()),
2117            Emit::LlvmIr,
2118            AllowUnused::No,
2119            LinkToAux::Yes,
2120            Vec::new(),
2121        );
2122
2123        let proc_res = self.compose_and_run_compiler(rustc, None);
2124        (proc_res, output_path)
2125    }
2126
2127    fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
2128        let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
2129        filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
2130
2131        // Because we use custom prefixes, we also have to register the default prefix.
2132        filecheck.arg("--check-prefix=CHECK");
2133
2134        // FIXME(#134510): auto-registering revision names as check prefix is a bit sketchy, and
2135        // that having to pass `--allow-unused-prefix` is an unfortunate side-effect of not knowing
2136        // whether the test author actually wanted revision-specific check prefixes or not.
2137        //
2138        // TL;DR We may not want to conflate `compiletest` revisions and `FileCheck` prefixes.
2139
2140        // HACK: tests are allowed to use a revision name as a check prefix.
2141        if let Some(rev) = self.revision {
2142            filecheck.arg("--check-prefix").arg(rev);
2143        }
2144
2145        // HACK: the filecheck tool normally fails if a prefix is defined but not used. However,
2146        // sometimes revisions are used to specify *compiletest* directives which are not FileCheck
2147        // concerns.
2148        filecheck.arg("--allow-unused-prefixes");
2149
2150        // Provide more context on failures.
2151        filecheck.args(&["--dump-input-context", "100"]);
2152
2153        // Add custom flags supplied by the `filecheck-flags:` test directive.
2154        filecheck.args(&self.props.filecheck_flags);
2155
2156        // FIXME(jieyouxu): don't pass an empty Path
2157        self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
2158    }
2159
2160    fn charset() -> &'static str {
2161        // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset
2162        if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
2163    }
2164
2165    fn compare_to_default_rustdoc(&self, out_dir: &Utf8Path) {
2166        if !self.config.has_html_tidy {
2167            return;
2168        }
2169        writeln!(self.stdout, "info: generating a diff against nightly rustdoc");
2170
2171        let suffix =
2172            self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2173        let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2174        remove_and_create_dir_all(&compare_dir).unwrap_or_else(|e| {
2175            panic!("failed to remove and recreate output directory `{compare_dir}`: {e}")
2176        });
2177
2178        // We need to create a new struct for the lifetimes on `config` to work.
2179        let new_rustdoc = TestCx {
2180            config: &Config {
2181                // FIXME: use beta or a user-specified rustdoc instead of
2182                // hardcoding the default toolchain
2183                rustdoc_path: Some("rustdoc".into()),
2184                // Needed for building auxiliary docs below
2185                rustc_path: "rustc".into(),
2186                ..self.config.clone()
2187            },
2188            ..*self
2189        };
2190
2191        let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2192        let mut rustc = new_rustdoc.make_compile_args(
2193            &new_rustdoc.testpaths.file,
2194            output_file,
2195            Emit::None,
2196            AllowUnused::Yes,
2197            LinkToAux::Yes,
2198            Vec::new(),
2199        );
2200        let aux_dir = new_rustdoc.aux_output_dir();
2201        new_rustdoc.build_all_auxiliary(&aux_dir, &mut rustc);
2202
2203        let proc_res = new_rustdoc.document(&compare_dir, DocKind::Html);
2204        if !proc_res.status.success() {
2205            writeln!(self.stderr, "failed to run nightly rustdoc");
2206            return;
2207        }
2208
2209        #[rustfmt::skip]
2210        let tidy_args = [
2211            "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar,rustdoc-topbar",
2212            "--indent", "yes",
2213            "--indent-spaces", "2",
2214            "--wrap", "0",
2215            "--show-warnings", "no",
2216            "--markup", "yes",
2217            "--quiet", "yes",
2218            "-modify",
2219        ];
2220        let tidy_dir = |dir| {
2221            for entry in walkdir::WalkDir::new(dir) {
2222                let entry = entry.expect("failed to read file");
2223                if entry.file_type().is_file()
2224                    && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2225                {
2226                    let status =
2227                        Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2228                    // `tidy` returns 1 if it modified the file.
2229                    assert!(status.success() || status.code() == Some(1));
2230                }
2231            }
2232        };
2233        tidy_dir(out_dir);
2234        tidy_dir(&compare_dir);
2235
2236        let pager = {
2237            let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2238            output.and_then(|out| {
2239                if out.status.success() {
2240                    Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2241                } else {
2242                    None
2243                }
2244            })
2245        };
2246
2247        let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2248
2249        if !write_filtered_diff(
2250            self,
2251            &diff_filename,
2252            out_dir,
2253            &compare_dir,
2254            self.config.verbose,
2255            |file_type, extension| {
2256                file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2257            },
2258        ) {
2259            return;
2260        }
2261
2262        match self.config.color {
2263            ColorConfig::AlwaysColor => colored::control::set_override(true),
2264            ColorConfig::NeverColor => colored::control::set_override(false),
2265            _ => {}
2266        }
2267
2268        if let Some(pager) = pager {
2269            let pager = pager.trim();
2270            if self.config.verbose {
2271                writeln!(self.stderr, "using pager {}", pager);
2272            }
2273            let output = Command::new(pager)
2274                // disable paging; we want this to be non-interactive
2275                .env("PAGER", "")
2276                .stdin(File::open(&diff_filename).unwrap())
2277                // Capture output and print it explicitly so it will in turn be
2278                // captured by output-capture.
2279                .output()
2280                .unwrap();
2281            assert!(output.status.success());
2282            writeln!(self.stdout, "{}", String::from_utf8_lossy(&output.stdout));
2283            writeln!(self.stderr, "{}", String::from_utf8_lossy(&output.stderr));
2284        } else {
2285            warning!("no pager configured, falling back to unified diff");
2286            help!(
2287                "try configuring a git pager (e.g. `delta`) with \
2288                `git config --global core.pager delta`"
2289            );
2290            let mut out = io::stdout();
2291            let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2292            let mut line = Vec::new();
2293            loop {
2294                line.truncate(0);
2295                match diff.read_until(b'\n', &mut line) {
2296                    Ok(0) => break,
2297                    Ok(_) => {}
2298                    Err(e) => writeln!(self.stderr, "ERROR: {:?}", e),
2299                }
2300                match String::from_utf8(line.clone()) {
2301                    Ok(line) => {
2302                        if line.starts_with('+') {
2303                            write!(&mut out, "{}", line.green()).unwrap();
2304                        } else if line.starts_with('-') {
2305                            write!(&mut out, "{}", line.red()).unwrap();
2306                        } else if line.starts_with('@') {
2307                            write!(&mut out, "{}", line.blue()).unwrap();
2308                        } else {
2309                            out.write_all(line.as_bytes()).unwrap();
2310                        }
2311                    }
2312                    Err(_) => {
2313                        write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2314                    }
2315                }
2316            }
2317        };
2318    }
2319
2320    fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
2321        let content = fs::read_to_string(path.as_std_path()).unwrap();
2322        let mut ignore = false;
2323        content
2324            .lines()
2325            .enumerate()
2326            .filter_map(|(line_nb, line)| {
2327                if (line.trim_start().starts_with("pub mod ")
2328                    || line.trim_start().starts_with("mod "))
2329                    && line.ends_with(';')
2330                {
2331                    if let Some(ref mut other_files) = other_files {
2332                        other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2333                    }
2334                    None
2335                } else {
2336                    let sline = line.rsplit("///").next().unwrap();
2337                    let line = sline.trim_start();
2338                    if line.starts_with("```") {
2339                        if ignore {
2340                            ignore = false;
2341                            None
2342                        } else {
2343                            ignore = true;
2344                            Some(line_nb + 1)
2345                        }
2346                    } else {
2347                        None
2348                    }
2349                }
2350            })
2351            .collect()
2352    }
2353
2354    /// This method is used for `//@ check-test-line-numbers-match`.
2355    ///
2356    /// It checks that doctests line in the displayed doctest "name" matches where they are
2357    /// defined in source code.
2358    fn check_rustdoc_test_option(&self, res: ProcRes) {
2359        let mut other_files = Vec::new();
2360        let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2361        let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2362        let normalized = normalized.to_str().unwrap().replace('\\', "/");
2363        files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2364        for other_file in other_files {
2365            let mut path = self.testpaths.file.clone();
2366            path.set_file_name(&format!("{}.rs", other_file));
2367            let path = path.canonicalize_utf8().expect("failed to canonicalize");
2368            let normalized = path.as_str().replace('\\', "/");
2369            files.insert(normalized, self.get_lines(&path, None));
2370        }
2371
2372        let mut tested = 0;
2373        for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2374            if let Some((left, right)) = s.split_once(" - ") {
2375                let path = left.rsplit("test ").next().unwrap();
2376                let path = fs::canonicalize(&path).expect("failed to canonicalize");
2377                let path = path.to_str().unwrap().replace('\\', "/");
2378                if let Some(ref mut v) = files.get_mut(&path) {
2379                    tested += 1;
2380                    let mut iter = right.split("(line ");
2381                    iter.next();
2382                    let line = iter
2383                        .next()
2384                        .unwrap_or(")")
2385                        .split(')')
2386                        .next()
2387                        .unwrap_or("0")
2388                        .parse()
2389                        .unwrap_or(0);
2390                    if let Ok(pos) = v.binary_search(&line) {
2391                        v.remove(pos);
2392                    } else {
2393                        self.fatal_proc_rec(
2394                            &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2395                            &res,
2396                        );
2397                    }
2398                }
2399            }
2400        }) {}
2401        if tested == 0 {
2402            self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2403        } else {
2404            for (entry, v) in &files {
2405                if !v.is_empty() {
2406                    self.fatal_proc_rec(
2407                        &format!(
2408                            "Not found test at line{} \"{}\":{:?}",
2409                            if v.len() > 1 { "s" } else { "" },
2410                            entry,
2411                            v
2412                        ),
2413                        &res,
2414                    );
2415                }
2416            }
2417        }
2418    }
2419
2420    fn force_color_svg(&self) -> bool {
2421        self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2422    }
2423
2424    fn load_compare_outputs(
2425        &self,
2426        proc_res: &ProcRes,
2427        output_kind: TestOutput,
2428        explicit_format: bool,
2429    ) -> usize {
2430        let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2431        let (stderr_kind, stdout_kind) = match output_kind {
2432            TestOutput::Compile => (
2433                if self.force_color_svg() {
2434                    if self.config.target.contains("windows") {
2435                        // We single out Windows here because some of the CLI coloring is
2436                        // specifically changed for Windows.
2437                        UI_WINDOWS_SVG
2438                    } else {
2439                        UI_SVG
2440                    }
2441                } else if self.props.stderr_per_bitwidth {
2442                    &stderr_bits
2443                } else {
2444                    UI_STDERR
2445                },
2446                UI_STDOUT,
2447            ),
2448            TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2449        };
2450
2451        let expected_stderr = self.load_expected_output(stderr_kind);
2452        let expected_stdout = self.load_expected_output(stdout_kind);
2453
2454        let mut normalized_stdout =
2455            self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2456        match output_kind {
2457            TestOutput::Run if self.config.remote_test_client.is_some() => {
2458                // When tests are run using the remote-test-client, the string
2459                // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"'
2460                // is printed to stdout by the client and then captured in the ProcRes,
2461                // so it needs to be removed when comparing the run-pass test execution output.
2462                normalized_stdout = static_regex!(
2463                    "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2464                )
2465                .replace(&normalized_stdout, "")
2466                .to_string();
2467                // When there is a panic, the remote-test-client also prints "died due to signal";
2468                // that needs to be removed as well.
2469                normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2470                    .replace(&normalized_stdout, "")
2471                    .to_string();
2472                // FIXME: it would be much nicer if we could just tell the remote-test-client to not
2473                // print these things.
2474            }
2475            _ => {}
2476        };
2477
2478        let stderr = if self.force_color_svg() {
2479            anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2480        } else if explicit_format {
2481            proc_res.stderr.clone()
2482        } else {
2483            json::extract_rendered(&proc_res.stderr)
2484        };
2485
2486        let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2487        let mut errors = 0;
2488        match output_kind {
2489            TestOutput::Compile => {
2490                if !self.props.dont_check_compiler_stdout {
2491                    if self
2492                        .compare_output(
2493                            stdout_kind,
2494                            &normalized_stdout,
2495                            &proc_res.stdout,
2496                            &expected_stdout,
2497                        )
2498                        .should_error()
2499                    {
2500                        errors += 1;
2501                    }
2502                }
2503                if !self.props.dont_check_compiler_stderr {
2504                    if self
2505                        .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2506                        .should_error()
2507                    {
2508                        errors += 1;
2509                    }
2510                }
2511            }
2512            TestOutput::Run => {
2513                if self
2514                    .compare_output(
2515                        stdout_kind,
2516                        &normalized_stdout,
2517                        &proc_res.stdout,
2518                        &expected_stdout,
2519                    )
2520                    .should_error()
2521                {
2522                    errors += 1;
2523                }
2524
2525                if self
2526                    .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2527                    .should_error()
2528                {
2529                    errors += 1;
2530                }
2531            }
2532        }
2533        errors
2534    }
2535
2536    fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2537        // Crude heuristic to detect when the output should have JSON-specific
2538        // normalization steps applied.
2539        let rflags = self.props.run_flags.join(" ");
2540        let cflags = self.props.compile_flags.join(" ");
2541        let json = rflags.contains("--format json")
2542            || rflags.contains("--format=json")
2543            || cflags.contains("--error-format json")
2544            || cflags.contains("--error-format pretty-json")
2545            || cflags.contains("--error-format=json")
2546            || cflags.contains("--error-format=pretty-json")
2547            || cflags.contains("--output-format json")
2548            || cflags.contains("--output-format=json");
2549
2550        let mut normalized = output.to_string();
2551
2552        let mut normalize_path = |from: &Utf8Path, to: &str| {
2553            let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
2554
2555            normalized = normalized.replace(from, to);
2556        };
2557
2558        let parent_dir = self.testpaths.file.parent().unwrap();
2559        normalize_path(parent_dir, "$DIR");
2560
2561        if self.props.remap_src_base {
2562            let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
2563            if self.testpaths.relative_dir != Utf8Path::new("") {
2564                remapped_parent_dir.push(&self.testpaths.relative_dir);
2565            }
2566            normalize_path(&remapped_parent_dir, "$DIR");
2567        }
2568
2569        let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
2570        // Fake paths into the libstd/libcore
2571        normalize_path(&base_dir.join("library"), "$SRC_DIR");
2572        // `ui-fulldeps` tests can show paths to the compiler source when testing macros from
2573        // `rustc_macros`
2574        // eg. /home/user/rust/compiler
2575        normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2576
2577        // Real paths into the libstd/libcore
2578        let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2579        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
2580        let rust_src_dir =
2581            rust_src_dir.read_link_utf8().unwrap_or_else(|_| rust_src_dir.to_path_buf());
2582        normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2583
2584        // Real paths into the compiler
2585        let rustc_src_dir = &self.config.sysroot_base.join("lib/rustlib/rustc-src/rust");
2586        rustc_src_dir.try_exists().expect(&*format!("{} should exists", rustc_src_dir));
2587        let rustc_src_dir = rustc_src_dir.read_link_utf8().unwrap_or(rustc_src_dir.to_path_buf());
2588        normalize_path(&rustc_src_dir.join("compiler"), "$COMPILER_DIR_REAL");
2589
2590        // eg.
2591        // /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
2592        normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2593        // Same as above, but with a canonicalized path.
2594        // This is required because some tests print canonical paths inside test build directory,
2595        // so if the build directory is a symlink, normalization doesn't help.
2596        //
2597        // NOTE: There are also tests which print the non-canonical name, so we need both this and
2598        // the above normalizations.
2599        normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
2600        // eg. /home/user/rust/build
2601        normalize_path(&self.config.build_root, "$BUILD_DIR");
2602
2603        if json {
2604            // escaped newlines in json strings should be readable
2605            // in the stderr files. There's no point in being correct,
2606            // since only humans process the stderr files.
2607            // Thus we just turn escaped newlines back into newlines.
2608            normalized = normalized.replace("\\n", "\n");
2609        }
2610
2611        // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them
2612        // with placeholders as we do not want tests needing updated when compiler source code
2613        // changes.
2614        // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL
2615        normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2616            .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2617            .into_owned();
2618
2619        normalized = Self::normalize_platform_differences(&normalized);
2620
2621        // Normalize long type name hash.
2622        normalized =
2623            static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2624                .replace_all(&normalized, |caps: &Captures<'_>| {
2625                    format!(
2626                        "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2627                        filename = &caps["filename"]
2628                    )
2629                })
2630                .into_owned();
2631
2632        // Normalize thread IDs in panic messages
2633        normalized = static_regex!(r"thread '(?P<name>.*?)' \((rtid )?\d+\) panicked")
2634            .replace_all(&normalized, "thread '$name' ($$TID) panicked")
2635            .into_owned();
2636
2637        normalized = normalized.replace("\t", "\\t"); // makes tabs visible
2638
2639        // Remove test annotations like `//~ ERROR text` from the output,
2640        // since they duplicate actual errors and make the output hard to read.
2641        // This mirrors the regex in src/tools/tidy/src/style.rs, please update
2642        // both if either are changed.
2643        normalized =
2644            static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2645
2646        // This code normalizes various hashes in v0 symbol mangling that is
2647        // emitted in the ui and mir-opt tests.
2648        let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2649        let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2650
2651        const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2652        if v0_crate_hash_prefix_re.is_match(&normalized) {
2653            // Normalize crate hash
2654            normalized =
2655                v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2656        }
2657
2658        let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2659        let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2660
2661        const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2662        if v0_back_ref_prefix_re.is_match(&normalized) {
2663            // Normalize back references (see RFC 2603)
2664            normalized =
2665                v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2666        }
2667
2668        // AllocId are numbered globally in a compilation session. This can lead to changes
2669        // depending on the exact compilation flags and host architecture. Meanwhile, we want
2670        // to keep them numbered, to see if the same id appears multiple times.
2671        // So we remap to deterministic numbers that only depend on the subset of allocations
2672        // that actually appear in the output.
2673        // We use uppercase ALLOC to distinguish from the non-normalized version.
2674        {
2675            let mut seen_allocs = indexmap::IndexSet::new();
2676
2677            // The alloc-id appears in pretty-printed allocations.
2678            normalized = static_regex!(
2679                r"╾─*a(lloc)?([0-9]+)(\+0x[0-9a-f]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2680            )
2681            .replace_all(&normalized, |caps: &Captures<'_>| {
2682                // Renumber the captured index.
2683                let index = caps.get(2).unwrap().as_str().to_string();
2684                let (index, _) = seen_allocs.insert_full(index);
2685                let offset = caps.get(3).map_or("", |c| c.as_str());
2686                let imm = caps.get(4).map_or("", |c| c.as_str());
2687                // Do not bother keeping it pretty, just make it deterministic.
2688                format!("╾ALLOC{index}{offset}{imm}╼")
2689            })
2690            .into_owned();
2691
2692            // The alloc-id appears in a sentence.
2693            normalized = static_regex!(r"\balloc([0-9]+)\b")
2694                .replace_all(&normalized, |caps: &Captures<'_>| {
2695                    let index = caps.get(1).unwrap().as_str().to_string();
2696                    let (index, _) = seen_allocs.insert_full(index);
2697                    format!("ALLOC{index}")
2698                })
2699                .into_owned();
2700        }
2701
2702        // Custom normalization rules
2703        for rule in custom_rules {
2704            let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2705            normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2706        }
2707        normalized
2708    }
2709
2710    /// Normalize output differences across platforms. Generally changes Windows output to be more
2711    /// Unix-like.
2712    ///
2713    /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings
2714    /// with LF.
2715    fn normalize_platform_differences(output: &str) -> String {
2716        let output = output.replace(r"\\", r"\");
2717
2718        // Used to find Windows paths.
2719        //
2720        // It's not possible to detect paths in the error messages generally, but this is a
2721        // decent enough heuristic.
2722        let re = static_regex!(
2723            r#"(?x)
2724                (?:
2725                  # Match paths that don't include spaces.
2726                  (?:\\[\pL\pN\.\-_']+)+\.\pL+
2727                |
2728                  # If the path starts with a well-known root, then allow spaces and no file extension.
2729                  \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2730                )"#
2731        );
2732        re.replace_all(&output, |caps: &Captures<'_>| caps[0].replace(r"\", "/"))
2733            .replace("\r\n", "\n")
2734    }
2735
2736    fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
2737        let mut path =
2738            expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2739
2740        if !path.exists() {
2741            if let Some(CompareMode::Polonius) = self.config.compare_mode {
2742                path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2743            }
2744        }
2745
2746        if !path.exists() {
2747            path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2748        }
2749
2750        path
2751    }
2752
2753    fn load_expected_output(&self, kind: &str) -> String {
2754        let path = self.expected_output_path(kind);
2755        if path.exists() {
2756            match self.load_expected_output_from_path(&path) {
2757                Ok(x) => x,
2758                Err(x) => self.fatal(&x),
2759            }
2760        } else {
2761            String::new()
2762        }
2763    }
2764
2765    fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
2766        fs::read_to_string(path)
2767            .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
2768    }
2769
2770    /// Attempts to delete a file, succeeding if the file does not exist.
2771    fn delete_file(&self, file: &Utf8Path) {
2772        if let Err(e) = fs::remove_file(file.as_std_path())
2773            && e.kind() != io::ErrorKind::NotFound
2774        {
2775            self.fatal(&format!("failed to delete `{}`: {}", file, e,));
2776        }
2777    }
2778
2779    fn compare_output(
2780        &self,
2781        stream: &str,
2782        actual: &str,
2783        actual_unnormalized: &str,
2784        expected: &str,
2785    ) -> CompareOutcome {
2786        let expected_path =
2787            expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
2788
2789        if self.config.bless && actual.is_empty() && expected_path.exists() {
2790            self.delete_file(&expected_path);
2791        }
2792
2793        let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2794            // FIXME: We ignore the first line of SVG files
2795            // because the width parameter is non-deterministic.
2796            (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2797            _ => expected != actual,
2798        };
2799        if !are_different {
2800            return CompareOutcome::Same;
2801        }
2802
2803        // Wrapper tools set by `runner` might provide extra output on failure,
2804        // for example a WebAssembly runtime might print the stack trace of an
2805        // `unreachable` instruction by default.
2806        //
2807        // Also, some tests like `ui/parallel-rustc` have non-deterministic
2808        // orders of output, so we need to compare by lines.
2809        let compare_output_by_lines =
2810            self.props.compare_output_by_lines || self.config.runner.is_some();
2811
2812        let tmp;
2813        let (expected, actual): (&str, &str) = if compare_output_by_lines {
2814            let actual_lines: HashSet<_> = actual.lines().collect();
2815            let expected_lines: Vec<_> = expected.lines().collect();
2816            let mut used = expected_lines.clone();
2817            used.retain(|line| actual_lines.contains(line));
2818            // check if `expected` contains a subset of the lines of `actual`
2819            if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2820                return CompareOutcome::Same;
2821            }
2822            if expected_lines.is_empty() {
2823                // if we have no lines to check, force a full overwite
2824                ("", actual)
2825            } else {
2826                tmp = (expected_lines.join("\n"), used.join("\n"));
2827                (&tmp.0, &tmp.1)
2828            }
2829        } else {
2830            (expected, actual)
2831        };
2832
2833        // Write the actual output to a file in build directory.
2834        let actual_path = self
2835            .output_base_name()
2836            .with_extra_extension(self.revision.unwrap_or(""))
2837            .with_extra_extension(
2838                self.config.compare_mode.as_ref().map(|cm| cm.to_str()).unwrap_or(""),
2839            )
2840            .with_extra_extension(stream);
2841
2842        if let Err(err) = fs::write(&actual_path, &actual) {
2843            self.fatal(&format!("failed to write {stream} to `{actual_path}`: {err}",));
2844        }
2845        writeln!(self.stdout, "Saved the actual {stream} to `{actual_path}`");
2846
2847        if !self.config.bless {
2848            if expected.is_empty() {
2849                writeln!(self.stdout, "normalized {}:\n{}\n", stream, actual);
2850            } else {
2851                self.show_diff(
2852                    stream,
2853                    &expected_path,
2854                    &actual_path,
2855                    expected,
2856                    actual,
2857                    actual_unnormalized,
2858                );
2859            }
2860        } else {
2861            // Delete non-revision .stderr/.stdout file if revisions are used.
2862            // Without this, we'd just generate the new files and leave the old files around.
2863            if self.revision.is_some() {
2864                let old =
2865                    expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2866                self.delete_file(&old);
2867            }
2868
2869            if !actual.is_empty() {
2870                if let Err(err) = fs::write(&expected_path, &actual) {
2871                    self.fatal(&format!("failed to write {stream} to `{expected_path}`: {err}"));
2872                }
2873                writeln!(
2874                    self.stdout,
2875                    "Blessing the {stream} of `{test_name}` as `{expected_path}`",
2876                    test_name = self.testpaths.file
2877                );
2878            }
2879        }
2880
2881        writeln!(self.stdout, "\nThe actual {stream} differed from the expected {stream}");
2882
2883        if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2884    }
2885
2886    /// Returns whether to show the full stderr/stdout.
2887    fn show_diff(
2888        &self,
2889        stream: &str,
2890        expected_path: &Utf8Path,
2891        actual_path: &Utf8Path,
2892        expected: &str,
2893        actual: &str,
2894        actual_unnormalized: &str,
2895    ) {
2896        writeln!(self.stderr, "diff of {stream}:\n");
2897        if let Some(diff_command) = self.config.diff_command.as_deref() {
2898            let mut args = diff_command.split_whitespace();
2899            let name = args.next().unwrap();
2900            match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2901                Err(err) => {
2902                    self.fatal(&format!(
2903                        "failed to call custom diff command `{diff_command}`: {err}"
2904                    ));
2905                }
2906                Ok(output) => {
2907                    let output = String::from_utf8_lossy(&output.stdout);
2908                    write!(self.stderr, "{output}");
2909                }
2910            }
2911        } else {
2912            write!(self.stderr, "{}", write_diff(expected, actual, 3));
2913        }
2914
2915        // NOTE: argument order is important, we need `actual` to be on the left so the line number match up when we compare it to `actual_unnormalized` below.
2916        let diff_results = make_diff(actual, expected, 0);
2917
2918        let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2919        for hunk in diff_results {
2920            let mut line_no = hunk.line_number;
2921            for line in hunk.lines {
2922                // NOTE: `Expected` is actually correct here, the argument order is reversed so our line numbers match up
2923                if let DiffLine::Expected(normalized) = line {
2924                    mismatches_normalized += &normalized;
2925                    mismatches_normalized += "\n";
2926                    mismatch_line_nos.push(line_no);
2927                    line_no += 1;
2928                }
2929            }
2930        }
2931        let mut mismatches_unnormalized = String::new();
2932        let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2933        for hunk in diff_normalized {
2934            if mismatch_line_nos.contains(&hunk.line_number) {
2935                for line in hunk.lines {
2936                    if let DiffLine::Resulting(unnormalized) = line {
2937                        mismatches_unnormalized += &unnormalized;
2938                        mismatches_unnormalized += "\n";
2939                    }
2940                }
2941            }
2942        }
2943
2944        let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2945        // HACK: instead of checking if each hunk is empty, this only checks if the whole input is empty. we should be smarter about this so we don't treat added or removed output as normalized.
2946        if !normalized_diff.is_empty()
2947            && !mismatches_unnormalized.is_empty()
2948            && !mismatches_normalized.is_empty()
2949        {
2950            writeln!(
2951                self.stderr,
2952                "Note: some mismatched output was normalized before being compared"
2953            );
2954            // FIXME: respect diff_command
2955            write!(
2956                self.stderr,
2957                "{}",
2958                write_diff(&mismatches_unnormalized, &mismatches_normalized, 0)
2959            );
2960        }
2961    }
2962
2963    fn check_and_prune_duplicate_outputs(
2964        &self,
2965        proc_res: &ProcRes,
2966        modes: &[CompareMode],
2967        require_same_modes: &[CompareMode],
2968    ) {
2969        for kind in UI_EXTENSIONS {
2970            let canon_comparison_path =
2971                expected_output_path(&self.testpaths, self.revision, &None, kind);
2972
2973            let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2974                Ok(canon) => canon,
2975                _ => continue,
2976            };
2977            let bless = self.config.bless;
2978            let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2979                let examined_path =
2980                    expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind);
2981
2982                // If there is no output, there is nothing to do
2983                let examined_content = match self.load_expected_output_from_path(&examined_path) {
2984                    Ok(content) => content,
2985                    _ => return,
2986                };
2987
2988                let is_duplicate = canon == examined_content;
2989
2990                match (bless, require_same, is_duplicate) {
2991                    // If we're blessing and the output is the same, then delete the file.
2992                    (true, _, true) => {
2993                        self.delete_file(&examined_path);
2994                    }
2995                    // If we want them to be the same, but they are different, then error.
2996                    // We do this whether we bless or not
2997                    (_, true, false) => {
2998                        self.fatal_proc_rec(
2999                            &format!("`{}` should not have different output from base test!", kind),
3000                            proc_res,
3001                        );
3002                    }
3003                    _ => {}
3004                }
3005            };
3006            for mode in modes {
3007                check_and_prune_duplicate_outputs(mode, false);
3008            }
3009            for mode in require_same_modes {
3010                check_and_prune_duplicate_outputs(mode, true);
3011            }
3012        }
3013    }
3014
3015    fn create_stamp(&self) {
3016        let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision);
3017        fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap();
3018    }
3019
3020    fn init_incremental_test(&self) {
3021        // (See `run_incremental_test` for an overview of how incremental tests work.)
3022
3023        // Before any of the revisions have executed, create the
3024        // incremental workproduct directory.  Delete any old
3025        // incremental work products that may be there from prior
3026        // runs.
3027        let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
3028        if incremental_dir.exists() {
3029            // Canonicalizing the path will convert it to the //?/ format
3030            // on Windows, which enables paths longer than 260 character
3031            let canonicalized = incremental_dir.canonicalize().unwrap();
3032            fs::remove_dir_all(canonicalized).unwrap();
3033        }
3034        fs::create_dir_all(&incremental_dir).unwrap();
3035
3036        if self.config.verbose {
3037            writeln!(self.stdout, "init_incremental_test: incremental_dir={incremental_dir}");
3038        }
3039    }
3040}
3041
3042struct ProcArgs {
3043    prog: OsString,
3044    args: Vec<OsString>,
3045}
3046
3047#[derive(Debug)]
3048pub struct ProcRes {
3049    status: ExitStatus,
3050    stdout: String,
3051    stderr: String,
3052    truncated: Truncated,
3053    cmdline: String,
3054}
3055
3056impl ProcRes {
3057    #[must_use]
3058    pub fn format_info(&self) -> String {
3059        fn render(name: &str, contents: &str) -> String {
3060            let contents = json::extract_rendered(contents);
3061            let contents = contents.trim_end();
3062            if contents.is_empty() {
3063                format!("{name}: none")
3064            } else {
3065                format!(
3066                    "\
3067                     --- {name} -------------------------------\n\
3068                     {contents}\n\
3069                     ------------------------------------------",
3070                )
3071            }
3072        }
3073
3074        format!(
3075            "status: {}\ncommand: {}\n{}\n{}\n",
3076            self.status,
3077            self.cmdline,
3078            render("stdout", &self.stdout),
3079            render("stderr", &self.stderr),
3080        )
3081    }
3082}
3083
3084#[derive(Debug)]
3085enum TargetLocation {
3086    ThisFile(Utf8PathBuf),
3087    ThisDirectory(Utf8PathBuf),
3088}
3089
3090enum AllowUnused {
3091    Yes,
3092    No,
3093}
3094
3095enum LinkToAux {
3096    Yes,
3097    No,
3098}
3099
3100#[derive(Debug, PartialEq)]
3101enum AuxType {
3102    Bin,
3103    Lib,
3104    Dylib,
3105    ProcMacro,
3106}
3107
3108/// Outcome of comparing a stream to a blessed file,
3109/// e.g. `.stderr` and `.fixed`.
3110#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3111enum CompareOutcome {
3112    /// Expected and actual outputs are the same
3113    Same,
3114    /// Outputs differed but were blessed
3115    Blessed,
3116    /// Outputs differed and an error should be emitted
3117    Differed,
3118}
3119
3120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3121enum DocKind {
3122    Html,
3123    Json,
3124}
3125
3126impl CompareOutcome {
3127    fn should_error(&self) -> bool {
3128        matches!(self, CompareOutcome::Differed)
3129    }
3130}