Skip to main content

compiletest/
runtest.rs

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