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