Skip to main content

compiletest/
runtest.rs

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