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