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