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        // Don't allow `unused_attributes` since these are usually actual mistakes, rather than just unused code.
1842        if let AllowUnused::Yes = allow_unused {
1843            rustc.args(&["-A", "unused", "-W", "unused_attributes"]);
1844        }
1845
1846        // Allow tests to use internal features.
1847        rustc.args(&["-A", "internal_features"]);
1848
1849        // Allow tests to have unused parens and braces.
1850        // Add #![deny(unused_parens, unused_braces)] to the test file if you want to
1851        // test that these lints are working.
1852        rustc.args(&["-A", "unused_parens"]);
1853        rustc.args(&["-A", "unused_braces"]);
1854
1855        if self.props.force_host {
1856            self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags);
1857            if !is_rustdoc {
1858                if let Some(ref linker) = self.config.host_linker {
1859                    rustc.arg(format!("-Clinker={}", linker));
1860                }
1861            }
1862        } else {
1863            self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags);
1864            if !is_rustdoc {
1865                if let Some(ref linker) = self.config.target_linker {
1866                    rustc.arg(format!("-Clinker={}", linker));
1867                }
1868            }
1869        }
1870
1871        // Use dynamic musl for tests because static doesn't allow creating dylibs
1872        if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1873            rustc.arg("-Ctarget-feature=-crt-static");
1874        }
1875
1876        if let LinkToAux::Yes = link_to_aux {
1877            // if we pass an `-L` argument to a directory that doesn't exist,
1878            // macOS ld emits warnings which disrupt the .stderr files
1879            if self.has_aux_dir() {
1880                rustc.arg("-L").arg(self.aux_output_dir_name());
1881            }
1882        }
1883
1884        // FIXME(jieyouxu): we should report a fatal error or warning if user wrote `-Cpanic=` with
1885        // something that's not `abort` and `-Cforce-unwind-tables` with a value that is not `yes`.
1886        //
1887        // We could apply these last and override any provided flags. That would ensure that the
1888        // build works, but some tests want to exercise that mixing panic modes in specific ways is
1889        // rejected. So we enable aborting panics and unwind tables before adding flags, just to
1890        // change the default.
1891        //
1892        // `minicore` requires `#![no_std]` and `#![no_core]`, which means no unwinding panics.
1893        if self.props.add_core_stubs {
1894            rustc.arg("-Cpanic=abort");
1895            rustc.arg("-Cforce-unwind-tables=yes");
1896        }
1897
1898        rustc.args(&self.props.compile_flags);
1899
1900        rustc
1901    }
1902
1903    fn make_exe_name(&self) -> Utf8PathBuf {
1904        // Using a single letter here to keep the path length down for
1905        // Windows.  Some test names get very long.  rustc creates `rcgu`
1906        // files with the module name appended to it which can more than
1907        // double the length.
1908        let mut f = self.output_base_dir().join("a");
1909        // FIXME: This is using the host architecture exe suffix, not target!
1910        if self.config.target.contains("emscripten") {
1911            f = f.with_extra_extension("js");
1912        } else if self.config.target.starts_with("wasm") {
1913            f = f.with_extra_extension("wasm");
1914        } else if self.config.target.contains("spirv") {
1915            f = f.with_extra_extension("spv");
1916        } else if !env::consts::EXE_SUFFIX.is_empty() {
1917            f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1918        }
1919        f
1920    }
1921
1922    fn make_run_args(&self) -> ProcArgs {
1923        // If we've got another tool to run under (valgrind),
1924        // then split apart its command
1925        let mut args = self.split_maybe_args(&self.config.runner);
1926
1927        let exe_file = self.make_exe_name();
1928
1929        args.push(exe_file.into_os_string());
1930
1931        // Add the arguments in the run_flags directive
1932        args.extend(self.props.run_flags.iter().map(OsString::from));
1933
1934        let prog = args.remove(0);
1935        ProcArgs { prog, args }
1936    }
1937
1938    fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1939        match *argstr {
1940            Some(ref s) => s
1941                .split(' ')
1942                .filter_map(|s| {
1943                    if s.chars().all(|c| c.is_whitespace()) {
1944                        None
1945                    } else {
1946                        Some(OsString::from(s))
1947                    }
1948                })
1949                .collect(),
1950            None => Vec::new(),
1951        }
1952    }
1953
1954    fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
1955        use crate::util;
1956
1957        // Linux and mac don't require adjusting the library search path
1958        if cfg!(unix) {
1959            format!("{:?}", command)
1960        } else {
1961            // Build the LD_LIBRARY_PATH variable as it would be seen on the command line
1962            // for diagnostic purposes
1963            fn lib_path_cmd_prefix(path: &str) -> String {
1964                format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
1965            }
1966
1967            format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
1968        }
1969    }
1970
1971    fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
1972        let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() };
1973
1974        self.dump_output_file(out, &format!("{}out", revision));
1975        self.dump_output_file(err, &format!("{}err", revision));
1976
1977        if !print_output {
1978            return;
1979        }
1980
1981        let path = Utf8Path::new(proc_name);
1982        let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
1983            String::from_iter(
1984                path.parent()
1985                    .unwrap()
1986                    .file_name()
1987                    .into_iter()
1988                    .chain(Some("/"))
1989                    .chain(path.file_name()),
1990            )
1991        } else {
1992            path.file_name().unwrap().into()
1993        };
1994        writeln!(self.stdout, "------{proc_name} stdout------------------------------");
1995        writeln!(self.stdout, "{}", out);
1996        writeln!(self.stdout, "------{proc_name} stderr------------------------------");
1997        writeln!(self.stdout, "{}", err);
1998        writeln!(self.stdout, "------------------------------------------");
1999    }
2000
2001    fn dump_output_file(&self, out: &str, extension: &str) {
2002        let outfile = self.make_out_name(extension);
2003        fs::write(outfile.as_std_path(), out)
2004            .unwrap_or_else(|err| panic!("failed to write {outfile}: {err:?}"));
2005    }
2006
2007    /// Creates a filename for output with the given extension.
2008    /// E.g., `/.../testname.revision.mode/testname.extension`.
2009    fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
2010        self.output_base_name().with_extension(extension)
2011    }
2012
2013    /// Gets the directory where auxiliary files are written.
2014    /// E.g., `/.../testname.revision.mode/auxiliary/`.
2015    fn aux_output_dir_name(&self) -> Utf8PathBuf {
2016        self.output_base_dir()
2017            .join("auxiliary")
2018            .with_extra_extension(self.config.mode.aux_dir_disambiguator())
2019    }
2020
2021    /// Gets the directory where auxiliary binaries are written.
2022    /// E.g., `/.../testname.revision.mode/auxiliary/bin`.
2023    fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
2024        self.aux_output_dir_name().join("bin")
2025    }
2026
2027    /// Generates a unique name for the test, such as `testname.revision.mode`.
2028    fn output_testname_unique(&self) -> Utf8PathBuf {
2029        output_testname_unique(self.config, self.testpaths, self.safe_revision())
2030    }
2031
2032    /// The revision, ignored for incremental compilation since it wants all revisions in
2033    /// the same directory.
2034    fn safe_revision(&self) -> Option<&str> {
2035        if self.config.mode == TestMode::Incremental { None } else { self.revision }
2036    }
2037
2038    /// Gets the absolute path to the directory where all output for the given
2039    /// test/revision should reside.
2040    /// E.g., `/path/to/build/host-tuple/test/ui/relative/testname.revision.mode/`.
2041    fn output_base_dir(&self) -> Utf8PathBuf {
2042        output_base_dir(self.config, self.testpaths, self.safe_revision())
2043    }
2044
2045    /// Gets the absolute path to the base filename used as output for the given
2046    /// test/revision.
2047    /// E.g., `/.../relative/testname.revision.mode/testname`.
2048    fn output_base_name(&self) -> Utf8PathBuf {
2049        output_base_name(self.config, self.testpaths, self.safe_revision())
2050    }
2051
2052    /// Prints a message to (captured) stdout if `config.verbose` is true.
2053    /// The message is also logged to `tracing::debug!` regardles of verbosity.
2054    ///
2055    /// Use `format_args!` as the argument to perform formatting if required.
2056    fn logv(&self, message: impl fmt::Display) {
2057        debug!("{message}");
2058        if self.config.verbose {
2059            // Note: `./x test ... --verbose --no-capture` is needed to see this print.
2060            writeln!(self.stdout, "{message}");
2061        }
2062    }
2063
2064    /// Prefix to print before error messages. Normally just `error`, but also
2065    /// includes the revision name for tests that use revisions.
2066    #[must_use]
2067    fn error_prefix(&self) -> String {
2068        match self.revision {
2069            Some(rev) => format!("error in revision `{rev}`"),
2070            None => format!("error"),
2071        }
2072    }
2073
2074    #[track_caller]
2075    fn fatal(&self, err: &str) -> ! {
2076        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2077        error!("fatal error, panic: {:?}", err);
2078        panic!("fatal error");
2079    }
2080
2081    fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
2082        self.fatal_proc_rec_general(err, None, proc_res, || ());
2083    }
2084
2085    /// Underlying implementation of [`Self::fatal_proc_rec`], providing some
2086    /// extra capabilities not needed by most callers.
2087    fn fatal_proc_rec_general(
2088        &self,
2089        err: &str,
2090        extra_note: Option<&str>,
2091        proc_res: &ProcRes,
2092        callback_before_unwind: impl FnOnce(),
2093    ) -> ! {
2094        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2095
2096        // Some callers want to print additional notes after the main error message.
2097        if let Some(note) = extra_note {
2098            writeln!(self.stdout, "{note}");
2099        }
2100
2101        // Print the details and output of the subprocess that caused this test to fail.
2102        writeln!(self.stdout, "{}", proc_res.format_info());
2103
2104        // Some callers want print more context or show a custom diff before the unwind occurs.
2105        callback_before_unwind();
2106
2107        // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from
2108        // compiletest, which is unnecessary noise.
2109        std::panic::resume_unwind(Box::new(()));
2110    }
2111
2112    // codegen tests (using FileCheck)
2113
2114    fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
2115        let output_path = self.output_base_name().with_extension("ll");
2116        let input_file = &self.testpaths.file;
2117        let rustc = self.make_compile_args(
2118            input_file,
2119            TargetLocation::ThisFile(output_path.clone()),
2120            Emit::LlvmIr,
2121            AllowUnused::No,
2122            LinkToAux::Yes,
2123            Vec::new(),
2124        );
2125
2126        let proc_res = self.compose_and_run_compiler(rustc, None, self.testpaths);
2127        (proc_res, output_path)
2128    }
2129
2130    fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
2131        let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
2132        filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
2133
2134        // Because we use custom prefixes, we also have to register the default prefix.
2135        filecheck.arg("--check-prefix=CHECK");
2136
2137        // FIXME(#134510): auto-registering revision names as check prefix is a bit sketchy, and
2138        // that having to pass `--allow-unused-prefix` is an unfortunate side-effect of not knowing
2139        // whether the test author actually wanted revision-specific check prefixes or not.
2140        //
2141        // TL;DR We may not want to conflate `compiletest` revisions and `FileCheck` prefixes.
2142
2143        // HACK: tests are allowed to use a revision name as a check prefix.
2144        if let Some(rev) = self.revision {
2145            filecheck.arg("--check-prefix").arg(rev);
2146        }
2147
2148        // HACK: the filecheck tool normally fails if a prefix is defined but not used. However,
2149        // sometimes revisions are used to specify *compiletest* directives which are not FileCheck
2150        // concerns.
2151        filecheck.arg("--allow-unused-prefixes");
2152
2153        // Provide more context on failures.
2154        filecheck.args(&["--dump-input-context", "100"]);
2155
2156        // Add custom flags supplied by the `filecheck-flags:` test directive.
2157        filecheck.args(&self.props.filecheck_flags);
2158
2159        // FIXME(jieyouxu): don't pass an empty Path
2160        self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
2161    }
2162
2163    fn charset() -> &'static str {
2164        // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset
2165        if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
2166    }
2167
2168    fn compare_to_default_rustdoc(&self, out_dir: &Utf8Path) {
2169        if !self.config.has_html_tidy {
2170            return;
2171        }
2172        writeln!(self.stdout, "info: generating a diff against nightly rustdoc");
2173
2174        let suffix =
2175            self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly");
2176        let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix));
2177        remove_and_create_dir_all(&compare_dir).unwrap_or_else(|e| {
2178            panic!("failed to remove and recreate output directory `{compare_dir}`: {e}")
2179        });
2180
2181        // We need to create a new struct for the lifetimes on `config` to work.
2182        let new_rustdoc = TestCx {
2183            config: &Config {
2184                // FIXME: use beta or a user-specified rustdoc instead of
2185                // hardcoding the default toolchain
2186                rustdoc_path: Some("rustdoc".into()),
2187                // Needed for building auxiliary docs below
2188                rustc_path: "rustc".into(),
2189                ..self.config.clone()
2190            },
2191            ..*self
2192        };
2193
2194        let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name());
2195        let mut rustc = new_rustdoc.make_compile_args(
2196            &new_rustdoc.testpaths.file,
2197            output_file,
2198            Emit::None,
2199            AllowUnused::Yes,
2200            LinkToAux::Yes,
2201            Vec::new(),
2202        );
2203        let aux_dir = new_rustdoc.aux_output_dir();
2204        new_rustdoc.build_all_auxiliary(&new_rustdoc.testpaths, &aux_dir, &mut rustc);
2205
2206        let proc_res = new_rustdoc.document(&compare_dir, &new_rustdoc.testpaths);
2207        if !proc_res.status.success() {
2208            writeln!(self.stderr, "failed to run nightly rustdoc");
2209            return;
2210        }
2211
2212        #[rustfmt::skip]
2213        let tidy_args = [
2214            "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar,rustdoc-topbar",
2215            "--indent", "yes",
2216            "--indent-spaces", "2",
2217            "--wrap", "0",
2218            "--show-warnings", "no",
2219            "--markup", "yes",
2220            "--quiet", "yes",
2221            "-modify",
2222        ];
2223        let tidy_dir = |dir| {
2224            for entry in walkdir::WalkDir::new(dir) {
2225                let entry = entry.expect("failed to read file");
2226                if entry.file_type().is_file()
2227                    && entry.path().extension().and_then(|p| p.to_str()) == Some("html")
2228                {
2229                    let status =
2230                        Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap();
2231                    // `tidy` returns 1 if it modified the file.
2232                    assert!(status.success() || status.code() == Some(1));
2233                }
2234            }
2235        };
2236        tidy_dir(out_dir);
2237        tidy_dir(&compare_dir);
2238
2239        let pager = {
2240            let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok();
2241            output.and_then(|out| {
2242                if out.status.success() {
2243                    Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager"))
2244                } else {
2245                    None
2246                }
2247            })
2248        };
2249
2250        let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id());
2251
2252        if !write_filtered_diff(
2253            self,
2254            &diff_filename,
2255            out_dir,
2256            &compare_dir,
2257            self.config.verbose,
2258            |file_type, extension| {
2259                file_type.is_file() && (extension == Some("html") || extension == Some("js"))
2260            },
2261        ) {
2262            return;
2263        }
2264
2265        match self.config.color {
2266            ColorConfig::AlwaysColor => colored::control::set_override(true),
2267            ColorConfig::NeverColor => colored::control::set_override(false),
2268            _ => {}
2269        }
2270
2271        if let Some(pager) = pager {
2272            let pager = pager.trim();
2273            if self.config.verbose {
2274                writeln!(self.stderr, "using pager {}", pager);
2275            }
2276            let output = Command::new(pager)
2277                // disable paging; we want this to be non-interactive
2278                .env("PAGER", "")
2279                .stdin(File::open(&diff_filename).unwrap())
2280                // Capture output and print it explicitly so it will in turn be
2281                // captured by output-capture.
2282                .output()
2283                .unwrap();
2284            assert!(output.status.success());
2285            writeln!(self.stdout, "{}", String::from_utf8_lossy(&output.stdout));
2286            writeln!(self.stderr, "{}", String::from_utf8_lossy(&output.stderr));
2287        } else {
2288            warning!("no pager configured, falling back to unified diff");
2289            help!(
2290                "try configuring a git pager (e.g. `delta`) with \
2291                `git config --global core.pager delta`"
2292            );
2293            let mut out = io::stdout();
2294            let mut diff = BufReader::new(File::open(&diff_filename).unwrap());
2295            let mut line = Vec::new();
2296            loop {
2297                line.truncate(0);
2298                match diff.read_until(b'\n', &mut line) {
2299                    Ok(0) => break,
2300                    Ok(_) => {}
2301                    Err(e) => writeln!(self.stderr, "ERROR: {:?}", e),
2302                }
2303                match String::from_utf8(line.clone()) {
2304                    Ok(line) => {
2305                        if line.starts_with('+') {
2306                            write!(&mut out, "{}", line.green()).unwrap();
2307                        } else if line.starts_with('-') {
2308                            write!(&mut out, "{}", line.red()).unwrap();
2309                        } else if line.starts_with('@') {
2310                            write!(&mut out, "{}", line.blue()).unwrap();
2311                        } else {
2312                            out.write_all(line.as_bytes()).unwrap();
2313                        }
2314                    }
2315                    Err(_) => {
2316                        write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap();
2317                    }
2318                }
2319            }
2320        };
2321    }
2322
2323    fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
2324        let content = fs::read_to_string(path.as_std_path()).unwrap();
2325        let mut ignore = false;
2326        content
2327            .lines()
2328            .enumerate()
2329            .filter_map(|(line_nb, line)| {
2330                if (line.trim_start().starts_with("pub mod ")
2331                    || line.trim_start().starts_with("mod "))
2332                    && line.ends_with(';')
2333                {
2334                    if let Some(ref mut other_files) = other_files {
2335                        other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2336                    }
2337                    None
2338                } else {
2339                    let sline = line.rsplit("///").next().unwrap();
2340                    let line = sline.trim_start();
2341                    if line.starts_with("```") {
2342                        if ignore {
2343                            ignore = false;
2344                            None
2345                        } else {
2346                            ignore = true;
2347                            Some(line_nb + 1)
2348                        }
2349                    } else {
2350                        None
2351                    }
2352                }
2353            })
2354            .collect()
2355    }
2356
2357    /// This method is used for `//@ check-test-line-numbers-match`.
2358    ///
2359    /// It checks that doctests line in the displayed doctest "name" matches where they are
2360    /// defined in source code.
2361    fn check_rustdoc_test_option(&self, res: ProcRes) {
2362        let mut other_files = Vec::new();
2363        let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2364        let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2365        let normalized = normalized.to_str().unwrap().replace('\\', "/");
2366        files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2367        for other_file in other_files {
2368            let mut path = self.testpaths.file.clone();
2369            path.set_file_name(&format!("{}.rs", other_file));
2370            let path = path.canonicalize_utf8().expect("failed to canonicalize");
2371            let normalized = path.as_str().replace('\\', "/");
2372            files.insert(normalized, self.get_lines(&path, None));
2373        }
2374
2375        let mut tested = 0;
2376        for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2377            if let Some((left, right)) = s.split_once(" - ") {
2378                let path = left.rsplit("test ").next().unwrap();
2379                let path = fs::canonicalize(&path).expect("failed to canonicalize");
2380                let path = path.to_str().unwrap().replace('\\', "/");
2381                if let Some(ref mut v) = files.get_mut(&path) {
2382                    tested += 1;
2383                    let mut iter = right.split("(line ");
2384                    iter.next();
2385                    let line = iter
2386                        .next()
2387                        .unwrap_or(")")
2388                        .split(')')
2389                        .next()
2390                        .unwrap_or("0")
2391                        .parse()
2392                        .unwrap_or(0);
2393                    if let Ok(pos) = v.binary_search(&line) {
2394                        v.remove(pos);
2395                    } else {
2396                        self.fatal_proc_rec(
2397                            &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2398                            &res,
2399                        );
2400                    }
2401                }
2402            }
2403        }) {}
2404        if tested == 0 {
2405            self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2406        } else {
2407            for (entry, v) in &files {
2408                if !v.is_empty() {
2409                    self.fatal_proc_rec(
2410                        &format!(
2411                            "Not found test at line{} \"{}\":{:?}",
2412                            if v.len() > 1 { "s" } else { "" },
2413                            entry,
2414                            v
2415                        ),
2416                        &res,
2417                    );
2418                }
2419            }
2420        }
2421    }
2422
2423    fn force_color_svg(&self) -> bool {
2424        self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2425    }
2426
2427    fn load_compare_outputs(
2428        &self,
2429        proc_res: &ProcRes,
2430        output_kind: TestOutput,
2431        explicit_format: bool,
2432    ) -> usize {
2433        let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2434        let (stderr_kind, stdout_kind) = match output_kind {
2435            TestOutput::Compile => (
2436                if self.force_color_svg() {
2437                    if self.config.target.contains("windows") {
2438                        // We single out Windows here because some of the CLI coloring is
2439                        // specifically changed for Windows.
2440                        UI_WINDOWS_SVG
2441                    } else {
2442                        UI_SVG
2443                    }
2444                } else if self.props.stderr_per_bitwidth {
2445                    &stderr_bits
2446                } else {
2447                    UI_STDERR
2448                },
2449                UI_STDOUT,
2450            ),
2451            TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2452        };
2453
2454        let expected_stderr = self.load_expected_output(stderr_kind);
2455        let expected_stdout = self.load_expected_output(stdout_kind);
2456
2457        let mut normalized_stdout =
2458            self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2459        match output_kind {
2460            TestOutput::Run if self.config.remote_test_client.is_some() => {
2461                // When tests are run using the remote-test-client, the string
2462                // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"'
2463                // is printed to stdout by the client and then captured in the ProcRes,
2464                // so it needs to be removed when comparing the run-pass test execution output.
2465                normalized_stdout = static_regex!(
2466                    "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2467                )
2468                .replace(&normalized_stdout, "")
2469                .to_string();
2470                // When there is a panic, the remote-test-client also prints "died due to signal";
2471                // that needs to be removed as well.
2472                normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2473                    .replace(&normalized_stdout, "")
2474                    .to_string();
2475                // FIXME: it would be much nicer if we could just tell the remote-test-client to not
2476                // print these things.
2477            }
2478            _ => {}
2479        };
2480
2481        let stderr = if self.force_color_svg() {
2482            anstyle_svg::Term::new().render_svg(&proc_res.stderr)
2483        } else if explicit_format {
2484            proc_res.stderr.clone()
2485        } else {
2486            json::extract_rendered(&proc_res.stderr)
2487        };
2488
2489        let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2490        let mut errors = 0;
2491        match output_kind {
2492            TestOutput::Compile => {
2493                if !self.props.dont_check_compiler_stdout {
2494                    if self
2495                        .compare_output(
2496                            stdout_kind,
2497                            &normalized_stdout,
2498                            &proc_res.stdout,
2499                            &expected_stdout,
2500                        )
2501                        .should_error()
2502                    {
2503                        errors += 1;
2504                    }
2505                }
2506                if !self.props.dont_check_compiler_stderr {
2507                    if self
2508                        .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2509                        .should_error()
2510                    {
2511                        errors += 1;
2512                    }
2513                }
2514            }
2515            TestOutput::Run => {
2516                if self
2517                    .compare_output(
2518                        stdout_kind,
2519                        &normalized_stdout,
2520                        &proc_res.stdout,
2521                        &expected_stdout,
2522                    )
2523                    .should_error()
2524                {
2525                    errors += 1;
2526                }
2527
2528                if self
2529                    .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2530                    .should_error()
2531                {
2532                    errors += 1;
2533                }
2534            }
2535        }
2536        errors
2537    }
2538
2539    fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2540        // Crude heuristic to detect when the output should have JSON-specific
2541        // normalization steps applied.
2542        let rflags = self.props.run_flags.join(" ");
2543        let cflags = self.props.compile_flags.join(" ");
2544        let json = rflags.contains("--format json")
2545            || rflags.contains("--format=json")
2546            || cflags.contains("--error-format json")
2547            || cflags.contains("--error-format pretty-json")
2548            || cflags.contains("--error-format=json")
2549            || cflags.contains("--error-format=pretty-json")
2550            || cflags.contains("--output-format json")
2551            || cflags.contains("--output-format=json");
2552
2553        let mut normalized = output.to_string();
2554
2555        let mut normalize_path = |from: &Utf8Path, to: &str| {
2556            let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
2557
2558            normalized = normalized.replace(from, to);
2559        };
2560
2561        let parent_dir = self.testpaths.file.parent().unwrap();
2562        normalize_path(parent_dir, "$DIR");
2563
2564        if self.props.remap_src_base {
2565            let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
2566            if self.testpaths.relative_dir != Utf8Path::new("") {
2567                remapped_parent_dir.push(&self.testpaths.relative_dir);
2568            }
2569            normalize_path(&remapped_parent_dir, "$DIR");
2570        }
2571
2572        let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
2573        // Fake paths into the libstd/libcore
2574        normalize_path(&base_dir.join("library"), "$SRC_DIR");
2575        // `ui-fulldeps` tests can show paths to the compiler source when testing macros from
2576        // `rustc_macros`
2577        // eg. /home/user/rust/compiler
2578        normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2579
2580        // Real paths into the libstd/libcore
2581        let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2582        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
2583        let rust_src_dir =
2584            rust_src_dir.read_link_utf8().unwrap_or_else(|_| rust_src_dir.to_path_buf());
2585        normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2586
2587        // Real paths into the compiler
2588        let rustc_src_dir = &self.config.sysroot_base.join("lib/rustlib/rustc-src/rust");
2589        rustc_src_dir.try_exists().expect(&*format!("{} should exists", rustc_src_dir));
2590        let rustc_src_dir = rustc_src_dir.read_link_utf8().unwrap_or(rustc_src_dir.to_path_buf());
2591        normalize_path(&rustc_src_dir.join("compiler"), "$COMPILER_DIR_REAL");
2592
2593        // eg.
2594        // /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
2595        normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2596        // Same as above, but with a canonicalized path.
2597        // This is required because some tests print canonical paths inside test build directory,
2598        // so if the build directory is a symlink, normalization doesn't help.
2599        //
2600        // NOTE: There are also tests which print the non-canonical name, so we need both this and
2601        // the above normalizations.
2602        normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
2603        // eg. /home/user/rust/build
2604        normalize_path(&self.config.build_root, "$BUILD_DIR");
2605
2606        if json {
2607            // escaped newlines in json strings should be readable
2608            // in the stderr files. There's no point in being correct,
2609            // since only humans process the stderr files.
2610            // Thus we just turn escaped newlines back into newlines.
2611            normalized = normalized.replace("\\n", "\n");
2612        }
2613
2614        // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them
2615        // with placeholders as we do not want tests needing updated when compiler source code
2616        // changes.
2617        // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL
2618        normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2619            .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2620            .into_owned();
2621
2622        normalized = Self::normalize_platform_differences(&normalized);
2623
2624        // Normalize long type name hash.
2625        normalized =
2626            static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2627                .replace_all(&normalized, |caps: &Captures<'_>| {
2628                    format!(
2629                        "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2630                        filename = &caps["filename"]
2631                    )
2632                })
2633                .into_owned();
2634
2635        // Normalize thread IDs in panic messages
2636        normalized = static_regex!(r"thread '(?P<name>.*?)' \((rtid )?\d+\) panicked")
2637            .replace_all(&normalized, "thread '$name' ($$TID) panicked")
2638            .into_owned();
2639
2640        normalized = normalized.replace("\t", "\\t"); // makes tabs visible
2641
2642        // Remove test annotations like `//~ ERROR text` from the output,
2643        // since they duplicate actual errors and make the output hard to read.
2644        // This mirrors the regex in src/tools/tidy/src/style.rs, please update
2645        // both if either are changed.
2646        normalized =
2647            static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2648
2649        // This code normalizes various hashes in v0 symbol mangling that is
2650        // emitted in the ui and mir-opt tests.
2651        let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2652        let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2653
2654        const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2655        if v0_crate_hash_prefix_re.is_match(&normalized) {
2656            // Normalize crate hash
2657            normalized =
2658                v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2659        }
2660
2661        let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2662        let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2663
2664        const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2665        if v0_back_ref_prefix_re.is_match(&normalized) {
2666            // Normalize back references (see RFC 2603)
2667            normalized =
2668                v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2669        }
2670
2671        // AllocId are numbered globally in a compilation session. This can lead to changes
2672        // depending on the exact compilation flags and host architecture. Meanwhile, we want
2673        // to keep them numbered, to see if the same id appears multiple times.
2674        // So we remap to deterministic numbers that only depend on the subset of allocations
2675        // that actually appear in the output.
2676        // We use uppercase ALLOC to distinguish from the non-normalized version.
2677        {
2678            let mut seen_allocs = indexmap::IndexSet::new();
2679
2680            // The alloc-id appears in pretty-printed allocations.
2681            normalized = static_regex!(
2682                r"╾─*a(lloc)?([0-9]+)(\+0x[0-9a-f]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2683            )
2684            .replace_all(&normalized, |caps: &Captures<'_>| {
2685                // Renumber the captured index.
2686                let index = caps.get(2).unwrap().as_str().to_string();
2687                let (index, _) = seen_allocs.insert_full(index);
2688                let offset = caps.get(3).map_or("", |c| c.as_str());
2689                let imm = caps.get(4).map_or("", |c| c.as_str());
2690                // Do not bother keeping it pretty, just make it deterministic.
2691                format!("╾ALLOC{index}{offset}{imm}╼")
2692            })
2693            .into_owned();
2694
2695            // The alloc-id appears in a sentence.
2696            normalized = static_regex!(r"\balloc([0-9]+)\b")
2697                .replace_all(&normalized, |caps: &Captures<'_>| {
2698                    let index = caps.get(1).unwrap().as_str().to_string();
2699                    let (index, _) = seen_allocs.insert_full(index);
2700                    format!("ALLOC{index}")
2701                })
2702                .into_owned();
2703        }
2704
2705        // Custom normalization rules
2706        for rule in custom_rules {
2707            let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2708            normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2709        }
2710        normalized
2711    }
2712
2713    /// Normalize output differences across platforms. Generally changes Windows output to be more
2714    /// Unix-like.
2715    ///
2716    /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings
2717    /// with LF.
2718    fn normalize_platform_differences(output: &str) -> String {
2719        let output = output.replace(r"\\", r"\");
2720
2721        // Used to find Windows paths.
2722        //
2723        // It's not possible to detect paths in the error messages generally, but this is a
2724        // decent enough heuristic.
2725        let re = static_regex!(
2726            r#"(?x)
2727                (?:
2728                  # Match paths that don't include spaces.
2729                  (?:\\[\pL\pN\.\-_']+)+\.\pL+
2730                |
2731                  # If the path starts with a well-known root, then allow spaces and no file extension.
2732                  \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2733                )"#
2734        );
2735        re.replace_all(&output, |caps: &Captures<'_>| caps[0].replace(r"\", "/"))
2736            .replace("\r\n", "\n")
2737    }
2738
2739    fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
2740        let mut path =
2741            expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
2742
2743        if !path.exists() {
2744            if let Some(CompareMode::Polonius) = self.config.compare_mode {
2745                path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2746            }
2747        }
2748
2749        if !path.exists() {
2750            path = expected_output_path(&self.testpaths, self.revision, &None, kind);
2751        }
2752
2753        path
2754    }
2755
2756    fn load_expected_output(&self, kind: &str) -> String {
2757        let path = self.expected_output_path(kind);
2758        if path.exists() {
2759            match self.load_expected_output_from_path(&path) {
2760                Ok(x) => x,
2761                Err(x) => self.fatal(&x),
2762            }
2763        } else {
2764            String::new()
2765        }
2766    }
2767
2768    fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
2769        fs::read_to_string(path)
2770            .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
2771    }
2772
2773    fn delete_file(&self, file: &Utf8Path) {
2774        if !file.exists() {
2775            // Deleting a nonexistent file would error.
2776            return;
2777        }
2778        if let Err(e) = fs::remove_file(file.as_std_path()) {
2779            self.fatal(&format!("failed to delete `{}`: {}", file, e,));
2780        }
2781    }
2782
2783    fn compare_output(
2784        &self,
2785        stream: &str,
2786        actual: &str,
2787        actual_unnormalized: &str,
2788        expected: &str,
2789    ) -> CompareOutcome {
2790        let expected_path =
2791            expected_output_path(self.testpaths, self.revision, &self.config.compare_mode, stream);
2792
2793        if self.config.bless && actual.is_empty() && expected_path.exists() {
2794            self.delete_file(&expected_path);
2795        }
2796
2797        let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2798            // FIXME: We ignore the first line of SVG files
2799            // because the width parameter is non-deterministic.
2800            (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2801            _ => expected != actual,
2802        };
2803        if !are_different {
2804            return CompareOutcome::Same;
2805        }
2806
2807        // Wrapper tools set by `runner` might provide extra output on failure,
2808        // for example a WebAssembly runtime might print the stack trace of an
2809        // `unreachable` instruction by default.
2810        //
2811        // Also, some tests like `ui/parallel-rustc` have non-deterministic
2812        // orders of output, so we need to compare by lines.
2813        let compare_output_by_lines =
2814            self.props.compare_output_by_lines || self.config.runner.is_some();
2815
2816        let tmp;
2817        let (expected, actual): (&str, &str) = if compare_output_by_lines {
2818            let actual_lines: HashSet<_> = actual.lines().collect();
2819            let expected_lines: Vec<_> = expected.lines().collect();
2820            let mut used = expected_lines.clone();
2821            used.retain(|line| actual_lines.contains(line));
2822            // check if `expected` contains a subset of the lines of `actual`
2823            if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2824                return CompareOutcome::Same;
2825            }
2826            if expected_lines.is_empty() {
2827                // if we have no lines to check, force a full overwite
2828                ("", actual)
2829            } else {
2830                tmp = (expected_lines.join("\n"), used.join("\n"));
2831                (&tmp.0, &tmp.1)
2832            }
2833        } else {
2834            (expected, actual)
2835        };
2836
2837        // Write the actual output to a file in build directory.
2838        let actual_path = self
2839            .output_base_name()
2840            .with_extra_extension(self.revision.unwrap_or(""))
2841            .with_extra_extension(
2842                self.config.compare_mode.as_ref().map(|cm| cm.to_str()).unwrap_or(""),
2843            )
2844            .with_extra_extension(stream);
2845
2846        if let Err(err) = fs::write(&actual_path, &actual) {
2847            self.fatal(&format!("failed to write {stream} to `{actual_path}`: {err}",));
2848        }
2849        writeln!(self.stdout, "Saved the actual {stream} to `{actual_path}`");
2850
2851        if !self.config.bless {
2852            if expected.is_empty() {
2853                writeln!(self.stdout, "normalized {}:\n{}\n", stream, actual);
2854            } else {
2855                self.show_diff(
2856                    stream,
2857                    &expected_path,
2858                    &actual_path,
2859                    expected,
2860                    actual,
2861                    actual_unnormalized,
2862                );
2863            }
2864        } else {
2865            // Delete non-revision .stderr/.stdout file if revisions are used.
2866            // Without this, we'd just generate the new files and leave the old files around.
2867            if self.revision.is_some() {
2868                let old =
2869                    expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2870                self.delete_file(&old);
2871            }
2872
2873            if !actual.is_empty() {
2874                if let Err(err) = fs::write(&expected_path, &actual) {
2875                    self.fatal(&format!("failed to write {stream} to `{expected_path}`: {err}"));
2876                }
2877                writeln!(
2878                    self.stdout,
2879                    "Blessing the {stream} of `{test_name}` as `{expected_path}`",
2880                    test_name = self.testpaths.file
2881                );
2882            }
2883        }
2884
2885        writeln!(self.stdout, "\nThe actual {stream} differed from the expected {stream}");
2886
2887        if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2888    }
2889
2890    /// Returns whether to show the full stderr/stdout.
2891    fn show_diff(
2892        &self,
2893        stream: &str,
2894        expected_path: &Utf8Path,
2895        actual_path: &Utf8Path,
2896        expected: &str,
2897        actual: &str,
2898        actual_unnormalized: &str,
2899    ) {
2900        writeln!(self.stderr, "diff of {stream}:\n");
2901        if let Some(diff_command) = self.config.diff_command.as_deref() {
2902            let mut args = diff_command.split_whitespace();
2903            let name = args.next().unwrap();
2904            match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2905                Err(err) => {
2906                    self.fatal(&format!(
2907                        "failed to call custom diff command `{diff_command}`: {err}"
2908                    ));
2909                }
2910                Ok(output) => {
2911                    let output = String::from_utf8_lossy(&output.stdout);
2912                    write!(self.stderr, "{output}");
2913                }
2914            }
2915        } else {
2916            write!(self.stderr, "{}", write_diff(expected, actual, 3));
2917        }
2918
2919        // 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.
2920        let diff_results = make_diff(actual, expected, 0);
2921
2922        let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2923        for hunk in diff_results {
2924            let mut line_no = hunk.line_number;
2925            for line in hunk.lines {
2926                // NOTE: `Expected` is actually correct here, the argument order is reversed so our line numbers match up
2927                if let DiffLine::Expected(normalized) = line {
2928                    mismatches_normalized += &normalized;
2929                    mismatches_normalized += "\n";
2930                    mismatch_line_nos.push(line_no);
2931                    line_no += 1;
2932                }
2933            }
2934        }
2935        let mut mismatches_unnormalized = String::new();
2936        let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2937        for hunk in diff_normalized {
2938            if mismatch_line_nos.contains(&hunk.line_number) {
2939                for line in hunk.lines {
2940                    if let DiffLine::Resulting(unnormalized) = line {
2941                        mismatches_unnormalized += &unnormalized;
2942                        mismatches_unnormalized += "\n";
2943                    }
2944                }
2945            }
2946        }
2947
2948        let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2949        // 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.
2950        if !normalized_diff.is_empty()
2951            && !mismatches_unnormalized.is_empty()
2952            && !mismatches_normalized.is_empty()
2953        {
2954            writeln!(
2955                self.stderr,
2956                "Note: some mismatched output was normalized before being compared"
2957            );
2958            // FIXME: respect diff_command
2959            write!(
2960                self.stderr,
2961                "{}",
2962                write_diff(&mismatches_unnormalized, &mismatches_normalized, 0)
2963            );
2964        }
2965    }
2966
2967    fn check_and_prune_duplicate_outputs(
2968        &self,
2969        proc_res: &ProcRes,
2970        modes: &[CompareMode],
2971        require_same_modes: &[CompareMode],
2972    ) {
2973        for kind in UI_EXTENSIONS {
2974            let canon_comparison_path =
2975                expected_output_path(&self.testpaths, self.revision, &None, kind);
2976
2977            let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2978                Ok(canon) => canon,
2979                _ => continue,
2980            };
2981            let bless = self.config.bless;
2982            let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2983                let examined_path =
2984                    expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind);
2985
2986                // If there is no output, there is nothing to do
2987                let examined_content = match self.load_expected_output_from_path(&examined_path) {
2988                    Ok(content) => content,
2989                    _ => return,
2990                };
2991
2992                let is_duplicate = canon == examined_content;
2993
2994                match (bless, require_same, is_duplicate) {
2995                    // If we're blessing and the output is the same, then delete the file.
2996                    (true, _, true) => {
2997                        self.delete_file(&examined_path);
2998                    }
2999                    // If we want them to be the same, but they are different, then error.
3000                    // We do this wether we bless or not
3001                    (_, true, false) => {
3002                        self.fatal_proc_rec(
3003                            &format!("`{}` should not have different output from base test!", kind),
3004                            proc_res,
3005                        );
3006                    }
3007                    _ => {}
3008                }
3009            };
3010            for mode in modes {
3011                check_and_prune_duplicate_outputs(mode, false);
3012            }
3013            for mode in require_same_modes {
3014                check_and_prune_duplicate_outputs(mode, true);
3015            }
3016        }
3017    }
3018
3019    fn create_stamp(&self) {
3020        let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.revision);
3021        fs::write(&stamp_file_path, compute_stamp_hash(&self.config)).unwrap();
3022    }
3023
3024    fn init_incremental_test(&self) {
3025        // (See `run_incremental_test` for an overview of how incremental tests work.)
3026
3027        // Before any of the revisions have executed, create the
3028        // incremental workproduct directory.  Delete any old
3029        // incremental work products that may be there from prior
3030        // runs.
3031        let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
3032        if incremental_dir.exists() {
3033            // Canonicalizing the path will convert it to the //?/ format
3034            // on Windows, which enables paths longer than 260 character
3035            let canonicalized = incremental_dir.canonicalize().unwrap();
3036            fs::remove_dir_all(canonicalized).unwrap();
3037        }
3038        fs::create_dir_all(&incremental_dir).unwrap();
3039
3040        if self.config.verbose {
3041            writeln!(self.stdout, "init_incremental_test: incremental_dir={incremental_dir}");
3042        }
3043    }
3044}
3045
3046struct ProcArgs {
3047    prog: OsString,
3048    args: Vec<OsString>,
3049}
3050
3051#[derive(Debug)]
3052pub struct ProcRes {
3053    status: ExitStatus,
3054    stdout: String,
3055    stderr: String,
3056    truncated: Truncated,
3057    cmdline: String,
3058}
3059
3060impl ProcRes {
3061    #[must_use]
3062    pub fn format_info(&self) -> String {
3063        fn render(name: &str, contents: &str) -> String {
3064            let contents = json::extract_rendered(contents);
3065            let contents = contents.trim_end();
3066            if contents.is_empty() {
3067                format!("{name}: none")
3068            } else {
3069                format!(
3070                    "\
3071                     --- {name} -------------------------------\n\
3072                     {contents}\n\
3073                     ------------------------------------------",
3074                )
3075            }
3076        }
3077
3078        format!(
3079            "status: {}\ncommand: {}\n{}\n{}\n",
3080            self.status,
3081            self.cmdline,
3082            render("stdout", &self.stdout),
3083            render("stderr", &self.stderr),
3084        )
3085    }
3086}
3087
3088#[derive(Debug)]
3089enum TargetLocation {
3090    ThisFile(Utf8PathBuf),
3091    ThisDirectory(Utf8PathBuf),
3092}
3093
3094enum AllowUnused {
3095    Yes,
3096    No,
3097}
3098
3099enum LinkToAux {
3100    Yes,
3101    No,
3102}
3103
3104#[derive(Debug, PartialEq)]
3105enum AuxType {
3106    Bin,
3107    Lib,
3108    Dylib,
3109    ProcMacro,
3110}
3111
3112/// Outcome of comparing a stream to a blessed file,
3113/// e.g. `.stderr` and `.fixed`.
3114#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3115enum CompareOutcome {
3116    /// Expected and actual outputs are the same
3117    Same,
3118    /// Outputs differed but were blessed
3119    Blessed,
3120    /// Outputs differed and an error should be emitted
3121    Differed,
3122}
3123
3124impl CompareOutcome {
3125    fn should_error(&self) -> bool {
3126        matches!(self, CompareOutcome::Differed)
3127    }
3128}