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