compiletest/
runtest.rs

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