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