Skip to main content

compiletest/
runtest.rs

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