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 JSON 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 JSON 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            aux_props.force_host = true;
1355        }
1356        let mut aux_dir = aux_dir.to_path_buf();
1357        if aux_type == Some(AuxType::Bin) {
1358            // On unix, the binary of `auxiliary/foo.rs` will be named
1359            // `auxiliary/foo` which clashes with the _dir_ `auxiliary/foo`, so
1360            // put bins in a `bin` subfolder.
1361            aux_dir.push("bin");
1362        }
1363        let aux_output = TargetLocation::ThisDirectory(aux_dir.clone());
1364        let aux_cx = TestCx {
1365            config: self.config,
1366            stdout: self.stdout,
1367            stderr: self.stderr,
1368            props: &aux_props,
1369            testpaths: self.testpaths,
1370            variant: self.variant,
1371        };
1372        // Create the directory for the stdout/stderr files.
1373        create_dir_all(aux_cx.output_base_dir()).unwrap();
1374        let mut aux_rustc = aux_cx.make_compile_args(
1375            // Always use `rustc` for aux crates, even in rustdoc tests.
1376            CompilerKind::Rustc,
1377            &aux_path,
1378            aux_output,
1379            Emit::None,
1380            AllowUnused::No,
1381            LinkToAux::No,
1382            Vec::new(),
1383        );
1384        aux_cx.build_all_auxiliary(&aux_dir, &mut aux_rustc);
1385
1386        aux_rustc.envs(aux_props.rustc_env.clone());
1387        for key in &aux_props.unset_rustc_env {
1388            aux_rustc.env_remove(key);
1389        }
1390
1391        let (aux_type, crate_type) = if aux_type == Some(AuxType::Bin) {
1392            (AuxType::Bin, Some("bin"))
1393        } else if aux_type == Some(AuxType::ProcMacro) {
1394            (AuxType::ProcMacro, Some("proc-macro"))
1395        } else if aux_type.is_some() {
1396            panic!("aux_type {aux_type:?} not expected");
1397        } else if aux_props.no_prefer_dynamic {
1398            (AuxType::Lib, None)
1399        } else if self.config.target.contains("emscripten")
1400            || (self.config.target.contains("musl")
1401                && !aux_props.force_host
1402                && !self.config.host.contains("musl"))
1403            || self.config.target.contains("wasm32")
1404            || self.config.target.contains("nvptx")
1405            || self.is_vxworks_pure_static()
1406            || self.config.target.contains("bpf")
1407            || !self.config.target_cfg().dynamic_linking
1408            || matches!(self.config.mode, TestMode::CoverageMap | TestMode::CoverageRun)
1409        {
1410            // We primarily compile all auxiliary libraries as dynamic libraries
1411            // to avoid code size bloat and large binaries as much as possible
1412            // for the test suite (otherwise including libstd statically in all
1413            // executables takes up quite a bit of space).
1414            //
1415            // For targets like MUSL or Emscripten, however, there is no support for
1416            // dynamic libraries so we just go back to building a normal library. Note,
1417            // however, that for MUSL if the library is built with `force_host` then
1418            // it's ok to be a dylib as the host should always support dylibs.
1419            //
1420            // Coverage tests want static linking by default so that coverage
1421            // mappings in auxiliary libraries can be merged into the final
1422            // executable.
1423            (AuxType::Lib, Some("lib"))
1424        } else {
1425            (AuxType::Dylib, Some("dylib"))
1426        };
1427
1428        if let Some(crate_type) = crate_type {
1429            aux_rustc.args(&["--crate-type", crate_type]);
1430        }
1431
1432        if aux_type == AuxType::ProcMacro {
1433            // For convenience, but this only works on 2018.
1434            aux_rustc.args(&["--extern", "proc_macro"]);
1435        }
1436
1437        aux_rustc.arg("-L").arg(&aux_dir);
1438
1439        if aux_props.add_minicore {
1440            let minicore_path = self.build_minicore();
1441            aux_rustc.arg("--extern");
1442            aux_rustc.arg(&format!("minicore={}", minicore_path));
1443        }
1444
1445        let auxres = aux_cx.compose_and_run(
1446            aux_rustc,
1447            aux_cx.config.host_compile_lib_path.as_path(),
1448            Some(aux_dir.as_path()),
1449            None,
1450        );
1451        if !auxres.status.success() {
1452            self.fatal_proc_rec(
1453                &format!("auxiliary build of {aux_path} failed to compile: "),
1454                &auxres,
1455            );
1456        }
1457        aux_type
1458    }
1459
1460    fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
1461        let mut filter_paths_from_len = Vec::new();
1462        let mut add_path = |path: &Utf8Path| {
1463            let path = path.to_string();
1464            let windows = path.replace("\\", "\\\\");
1465            if windows != path {
1466                filter_paths_from_len.push(windows);
1467            }
1468            filter_paths_from_len.push(path);
1469        };
1470
1471        // List of paths that will not be measured when determining whether the output is larger
1472        // than the output truncation threshold.
1473        //
1474        // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the
1475        // same slice of text will be double counted and the truncation might not happen.
1476        add_path(&self.config.src_test_suite_root);
1477        add_path(&self.config.build_test_suite_root);
1478
1479        read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
1480    }
1481
1482    fn compose_and_run(
1483        &self,
1484        mut command: Command,
1485        lib_path: &Utf8Path,
1486        aux_path: Option<&Utf8Path>,
1487        input: Option<String>,
1488    ) -> ProcRes {
1489        let cmdline = {
1490            let cmdline = self.make_cmdline(&command, lib_path);
1491            self.logv(format_args!("executing {cmdline}"));
1492            cmdline
1493        };
1494
1495        command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped());
1496
1497        // Need to be sure to put both the lib_path and the aux path in the dylib
1498        // search path for the child.
1499        add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path));
1500
1501        let mut child = disable_error_reporting(|| command.spawn())
1502            .unwrap_or_else(|e| panic!("failed to exec `{command:?}`: {e:?}"));
1503        if let Some(input) = input {
1504            child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap();
1505        }
1506
1507        let (Output { status, stdout, stderr }, truncated) = self.read2_abbreviated(child);
1508
1509        let result = ProcRes {
1510            status,
1511            stdout: String::from_utf8_lossy(&stdout).into_owned(),
1512            stderr: String::from_utf8_lossy(&stderr).into_owned(),
1513            truncated,
1514            cmdline,
1515        };
1516
1517        self.dump_output(
1518            self.config.verbose || (!result.status.success() && self.config.mode != TestMode::Ui),
1519            &command.get_program().to_string_lossy(),
1520            &result.stdout,
1521            &result.stderr,
1522        );
1523
1524        result
1525    }
1526
1527    /// Choose a compiler kind (rustc or rustdoc) for compiling test files,
1528    /// based on the test suite being tested.
1529    fn compiler_kind_for_non_aux(&self) -> CompilerKind {
1530        match self.config.suite {
1531            TestSuite::RustdocJs | TestSuite::RustdocJson | TestSuite::RustdocUi => {
1532                CompilerKind::Rustdoc
1533            }
1534
1535            // Exhaustively match all other suites.
1536            // Note that some suites never actually use this method, so the
1537            // return value for those suites is not necessarily meaningful.
1538            TestSuite::AssemblyLlvm
1539            | TestSuite::BuildStd
1540            | TestSuite::CodegenLlvm
1541            | TestSuite::CodegenUnits
1542            | TestSuite::Coverage
1543            | TestSuite::CoverageRunRustdoc
1544            | TestSuite::Crashes
1545            | TestSuite::Debuginfo
1546            | TestSuite::Incremental
1547            | TestSuite::MirOpt
1548            | TestSuite::Pretty
1549            | TestSuite::RunMake
1550            | TestSuite::RunMakeCargo
1551            | TestSuite::RustdocGui
1552            | TestSuite::RustdocHtml
1553            | TestSuite::RustdocJsStd
1554            | TestSuite::Ui
1555            | TestSuite::UiFullDeps => CompilerKind::Rustc,
1556        }
1557    }
1558
1559    fn make_compile_args(
1560        &self,
1561        compiler_kind: CompilerKind,
1562        input_file: &Utf8Path,
1563        output_file: TargetLocation,
1564        emit: Emit,
1565        allow_unused: AllowUnused,
1566        link_to_aux: LinkToAux,
1567        passes: Vec<String>, // Vec of passes under mir-opt test to be dumped
1568    ) -> Command {
1569        // FIXME(Zalathar): We should have a cleaner distinction between
1570        // `rustc` flags, `rustdoc` flags, and flags shared by both.
1571        let mut compiler = match compiler_kind {
1572            CompilerKind::Rustc => Command::new(&self.config.rustc_path),
1573            CompilerKind::Rustdoc => {
1574                Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet"))
1575            }
1576        };
1577        compiler.arg(input_file);
1578
1579        // Use a single thread for efficiency and a deterministic error message order
1580        compiler.arg("-Zthreads=1");
1581
1582        // Hide libstd sources from ui tests to make sure we generate the stderr
1583        // output that users will see.
1584        // Without this, we may be producing good diagnostics in-tree but users
1585        // will not see half the information.
1586        //
1587        // This also has the benefit of more effectively normalizing output between different
1588        // compilers, so that we don't have to know the `/rustc/$sha` output to normalize after the
1589        // fact.
1590        compiler.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX");
1591        compiler.arg("-Ztranslate-remapped-path-to-local-path=no");
1592
1593        // Hide Cargo dependency sources from ui tests to make sure the error message doesn't
1594        // change depending on whether $CARGO_HOME is remapped or not. If this is not present,
1595        // when $CARGO_HOME is remapped the source won't be shown, and when it's not remapped the
1596        // source will be shown, causing a blessing hell.
1597        compiler.arg("-Z").arg(format!(
1598            "ignore-directory-in-diagnostics-source-blocks={}",
1599            home::cargo_home().expect("failed to find cargo home").to_str().unwrap()
1600        ));
1601        // Similarly, vendored sources shouldn't be shown when running from a dist tarball.
1602        compiler.arg("-Z").arg(format!(
1603            "ignore-directory-in-diagnostics-source-blocks={}",
1604            self.config.src_root.join("vendor"),
1605        ));
1606
1607        // Optionally prevent default --sysroot if specified in test compile-flags.
1608        //
1609        // FIXME: I feel like this logic is fairly sus.
1610        if !self.props.compile_flags.iter().any(|flag| flag.starts_with("--sysroot"))
1611            && !self.config.host_rustcflags.iter().any(|flag| flag == "--sysroot")
1612        {
1613            // In stage 0, make sure we use `stage0-sysroot` instead of the bootstrap sysroot.
1614            compiler.arg("--sysroot").arg(&self.config.sysroot_base);
1615        }
1616
1617        // If the provided codegen backend is not LLVM, we need to pass it.
1618        if let Some(ref backend) = self.config.override_codegen_backend {
1619            compiler.arg(format!("-Zcodegen-backend={}", backend));
1620        }
1621
1622        // Optionally prevent default --target if specified in test compile-flags.
1623        let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target"));
1624
1625        if !custom_target {
1626            let target =
1627                if self.props.force_host { &*self.config.host } else { &*self.config.target };
1628
1629            compiler.arg(&format!("--target={}", target));
1630            if target.ends_with(".json") {
1631                // `-Zunstable-options` is necessary when compiletest is running with custom targets
1632                // (such as synthetic targets used to bless mir-opt tests).
1633                compiler.arg("-Zunstable-options");
1634            }
1635        }
1636        self.set_revision_flags(&mut compiler);
1637
1638        if compiler_kind == CompilerKind::Rustc {
1639            if let Some(ref incremental_dir) = self.props.incremental_dir {
1640                compiler.args(&["-C", &format!("incremental={}", incremental_dir)]);
1641                compiler.args(&["-Z", "incremental-verify-ich"]);
1642            }
1643
1644            if self.config.mode == TestMode::CodegenUnits {
1645                compiler.args(&["-Z", "human_readable_cgu_names"]);
1646            }
1647
1648            if self.config.mode == TestMode::DebugInfo && cfg!(target_os = "windows") {
1649                // Prevent debugger processes from creating new console windows.
1650                compiler.args(&["-Z", r#"crate-attr=windows_subsystem="windows""#]);
1651            }
1652        }
1653
1654        if self.config.optimize_tests && compiler_kind == CompilerKind::Rustc {
1655            match self.config.mode {
1656                TestMode::Ui => {
1657                    // If optimize-tests is true we still only want to optimize tests that actually get
1658                    // executed and that don't specify their own optimization levels.
1659                    // Note: aux libs don't have a pass/fail mode, so they won't get optimized
1660                    // unless compile-flags are set in the aux file.
1661                    // FIXME(Zalathar): We could also optimize run-fail/run-crash tests,
1662                    // but it's unclear whether that would be helpful or a waste of time.
1663                    if self.effective_pass_fail_mode() == Some(PassFailMode::RunPass)
1664                        && !self
1665                            .props
1666                            .compile_flags
1667                            .iter()
1668                            .any(|arg| arg == "-O" || arg.contains("opt-level"))
1669                    {
1670                        compiler.arg("-O");
1671                    }
1672                }
1673                TestMode::DebugInfo => { /* debuginfo tests must be unoptimized */ }
1674                TestMode::CoverageMap | TestMode::CoverageRun => {
1675                    // Coverage mappings and coverage reports are affected by
1676                    // optimization level, so they ignore the optimize-tests
1677                    // setting and set an optimization level in their mode's
1678                    // compile flags (below) or in per-test `compile-flags`.
1679                }
1680                _ => {
1681                    compiler.arg("-O");
1682                }
1683            }
1684        }
1685
1686        let set_mir_dump_dir = |rustc: &mut Command| {
1687            let mir_dump_dir = self.output_base_dir();
1688            let mut dir_opt = "-Zdump-mir-dir=".to_string();
1689            dir_opt.push_str(mir_dump_dir.as_str());
1690            debug!("dir_opt: {:?}", dir_opt);
1691            rustc.arg(dir_opt);
1692        };
1693
1694        match self.config.mode {
1695            TestMode::Incremental => {
1696                // If we are extracting and matching errors in the new
1697                // fashion, then you want JSON mode. Old-skool error
1698                // patterns still match the raw compiler output.
1699                if self.props.error_patterns.is_empty()
1700                    && self.props.regex_error_patterns.is_empty()
1701                {
1702                    compiler.args(&["--error-format", "json"]);
1703                    compiler.args(&["--json", "future-incompat"]);
1704                }
1705                compiler.arg("-Zui-testing");
1706                compiler.arg("-Zdeduplicate-diagnostics=no");
1707            }
1708            TestMode::Ui => {
1709                if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) {
1710                    compiler.args(&["--error-format", "json"]);
1711                    compiler.args(&["--json", "future-incompat"]);
1712                }
1713                compiler.arg("-Ccodegen-units=1");
1714                // Hide line numbers to reduce churn
1715                compiler.arg("-Zui-testing");
1716                compiler.arg("-Zdeduplicate-diagnostics=no");
1717                compiler.arg("-Zwrite-long-types-to-disk=no");
1718                // FIXME: use this for other modes too, for perf?
1719                compiler.arg("-Cstrip=debuginfo");
1720
1721                if self.config.parallel_frontend_enabled() {
1722                    // Currently, we only use multiple threads for the UI test suite,
1723                    // because UI tests can effectively verify the parallel frontend and
1724                    // require minimal modification. The option will later be extended to
1725                    // other test suites.
1726                    compiler.arg(&format!("-Zthreads={}", self.config.parallel_frontend_threads));
1727                }
1728            }
1729            TestMode::MirOpt => {
1730                // We check passes under test to minimize the mir-opt test dump
1731                // if files_for_miropt_test parses the passes, we dump only those passes
1732                // otherwise we conservatively pass -Zdump-mir=all
1733                let zdump_arg = if !passes.is_empty() {
1734                    format!("-Zdump-mir={}", passes.join(" | "))
1735                } else {
1736                    "-Zdump-mir=all".to_string()
1737                };
1738
1739                compiler.args(&[
1740                    "-Copt-level=1",
1741                    &zdump_arg,
1742                    "-Zvalidate-mir",
1743                    "-Zlint-mir",
1744                    "-Zdump-mir-exclude-pass-number",
1745                    "-Zmir-include-spans=false", // remove span comments from NLL MIR dumps
1746                    "--crate-type=rlib",
1747                ]);
1748                if let Some(pass) = &self.props.mir_unit_test {
1749                    compiler
1750                        .args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]);
1751                } else {
1752                    compiler.args(&[
1753                        "-Zmir-opt-level=4",
1754                        "-Zmir-enable-passes=+ReorderBasicBlocks,+ReorderLocals",
1755                    ]);
1756                }
1757
1758                set_mir_dump_dir(&mut compiler);
1759            }
1760            TestMode::CoverageMap => {
1761                compiler.arg("-Cinstrument-coverage");
1762                // These tests only compile to LLVM IR, so they don't need the
1763                // profiler runtime to be present.
1764                compiler.arg("-Zno-profiler-runtime");
1765                // Coverage mappings are sensitive to MIR optimizations, and
1766                // the current snapshots assume `opt-level=2` unless overridden
1767                // by `compile-flags`.
1768                compiler.arg("-Copt-level=2");
1769            }
1770            TestMode::CoverageRun => {
1771                compiler.arg("-Cinstrument-coverage");
1772                // Coverage reports are sometimes sensitive to optimizations,
1773                // and the current snapshots assume `opt-level=2` unless
1774                // overridden by `compile-flags`.
1775                compiler.arg("-Copt-level=2");
1776            }
1777            TestMode::Assembly | TestMode::Codegen => {
1778                compiler.arg("-Cdebug-assertions=no");
1779                // For assembly and codegen tests, we want to use the same order
1780                // of the items of a codegen unit as the source order, so that
1781                // we can compare the output with the source code through filecheck.
1782                compiler.arg("-Zcodegen-source-order");
1783            }
1784            TestMode::Crashes => {
1785                set_mir_dump_dir(&mut compiler);
1786            }
1787            TestMode::CodegenUnits => {
1788                compiler.arg("-Zprint-mono-items");
1789            }
1790            TestMode::Pretty
1791            | TestMode::DebugInfo
1792            | TestMode::RustdocHtml
1793            | TestMode::RustdocJson
1794            | TestMode::RunMake
1795            | TestMode::RustdocJs => {
1796                // do not use JSON output
1797            }
1798        }
1799
1800        if self.props.remap_src_base {
1801            compiler.arg(format!(
1802                "--remap-path-prefix={}={}",
1803                self.config.src_test_suite_root, FAKE_SRC_BASE,
1804            ));
1805        }
1806
1807        if compiler_kind == CompilerKind::Rustc {
1808            match emit {
1809                Emit::None => {}
1810                Emit::Metadata => {
1811                    compiler.args(&["--emit", "metadata"]);
1812                }
1813                Emit::LlvmIr => {
1814                    compiler.args(&["--emit", "llvm-ir"]);
1815                }
1816                Emit::Mir => {
1817                    compiler.args(&["--emit", "mir"]);
1818                }
1819                Emit::Asm => {
1820                    compiler.args(&["--emit", "asm"]);
1821                }
1822                Emit::LinkArgsAsm => {
1823                    compiler.args(&["-Clink-args=--emit=asm"]);
1824                }
1825            }
1826        }
1827
1828        if compiler_kind == CompilerKind::Rustc {
1829            if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() {
1830                // rustc.arg("-g"); // get any backtrace at all on errors
1831            } else if !self.props.no_prefer_dynamic {
1832                compiler.args(&["-C", "prefer-dynamic"]);
1833            }
1834        }
1835
1836        match output_file {
1837            // If the test's compile flags specify an output path with `-o`,
1838            // avoid a compiler warning about `--out-dir` being ignored.
1839            _ if self.props.compile_flags.iter().any(|flag| flag == "-o") => {}
1840            TargetLocation::ThisFile(path) => {
1841                compiler.arg("-o").arg(path);
1842            }
1843            TargetLocation::ThisDirectory(path) => match compiler_kind {
1844                CompilerKind::Rustdoc => {
1845                    // `rustdoc` uses `-o` for the output directory.
1846                    compiler.arg("-o").arg(path);
1847                }
1848                CompilerKind::Rustc => {
1849                    compiler.arg("--out-dir").arg(path);
1850                }
1851            },
1852        }
1853
1854        match self.config.compare_mode {
1855            Some(CompareMode::Polonius) => {
1856                compiler.args(&["-Zpolonius=next"]);
1857            }
1858            Some(CompareMode::NextSolver) => {
1859                compiler.args(&["-Znext-solver"]);
1860            }
1861            Some(CompareMode::NextSolverCoherence) => {
1862                compiler.args(&["-Znext-solver=coherence"]);
1863            }
1864            Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => {
1865                compiler.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]);
1866            }
1867            Some(CompareMode::SplitDwarf) => {
1868                compiler.args(&["-Csplit-debuginfo=unpacked"]);
1869            }
1870            Some(CompareMode::SplitDwarfSingle) => {
1871                compiler.args(&["-Csplit-debuginfo=packed"]);
1872            }
1873            None => {}
1874        }
1875
1876        // Add `-A unused` before `config` flags and in-test (`props`) flags, so that they can
1877        // overwrite this.
1878        // Don't allow `unused_attributes` since these are usually actual mistakes, rather than just unused code.
1879        if let AllowUnused::Yes = allow_unused {
1880            compiler.args(&["-A", "unused", "-W", "unused_attributes"]);
1881        }
1882
1883        // Allow tests to use internal and incomplete features.
1884        compiler.args(&["-A", "internal_features"]);
1885        compiler.args(&["-A", "incomplete_features"]);
1886
1887        // Allow tests to have unused parens and braces.
1888        // Add #![deny(unused_parens, unused_braces)] to the test file if you want to
1889        // test that these lints are working.
1890        compiler.args(&["-A", "unused_parens"]);
1891        compiler.args(&["-A", "unused_braces"]);
1892
1893        if self.props.force_host {
1894            self.maybe_add_external_args(&mut compiler, &self.config.host_rustcflags);
1895            if compiler_kind == CompilerKind::Rustc
1896                && let Some(ref linker) = self.config.host_linker
1897            {
1898                compiler.arg(format!("-Clinker={linker}"));
1899            }
1900        } else {
1901            self.maybe_add_external_args(&mut compiler, &self.config.target_rustcflags);
1902            if compiler_kind == CompilerKind::Rustc
1903                && let Some(ref linker) = self.config.target_linker
1904            {
1905                compiler.arg(format!("-Clinker={linker}"));
1906            }
1907        }
1908
1909        // Use dynamic musl for tests because static doesn't allow creating dylibs
1910        if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() {
1911            compiler.arg("-Ctarget-feature=-crt-static");
1912        }
1913
1914        if let LinkToAux::Yes = link_to_aux {
1915            // if we pass an `-L` argument to a directory that doesn't exist,
1916            // macOS ld emits warnings which disrupt the .stderr files
1917            if self.has_aux_dir() {
1918                compiler.arg("-L").arg(self.aux_output_dir_name());
1919            }
1920        }
1921
1922        // FIXME(jieyouxu): we should report a fatal error or warning if user wrote `-Cpanic=` with
1923        // something that's not `abort` and `-Cforce-unwind-tables` with a value that is not `yes`.
1924        //
1925        // We could apply these last and override any provided flags. That would ensure that the
1926        // build works, but some tests want to exercise that mixing panic modes in specific ways is
1927        // rejected. So we enable aborting panics and unwind tables before adding flags, just to
1928        // change the default.
1929        //
1930        // `minicore` requires `#![no_std]` and `#![no_core]`, which means no unwinding panics.
1931        if self.props.add_minicore {
1932            compiler.arg("-Cpanic=abort");
1933            compiler.arg("-Cforce-unwind-tables=yes");
1934        }
1935
1936        compiler.args(&self.props.compile_flags);
1937
1938        compiler
1939    }
1940
1941    fn make_exe_name(&self) -> Utf8PathBuf {
1942        // Using a single letter here to keep the path length down for
1943        // Windows.  Some test names get very long.  rustc creates `rcgu`
1944        // files with the module name appended to it which can more than
1945        // double the length.
1946        let mut f = self.output_base_dir().join("a");
1947        // FIXME: This is using the host architecture exe suffix, not target!
1948        if self.config.target.contains("emscripten") {
1949            f = f.with_extra_extension("js");
1950        } else if self.config.target.starts_with("wasm") {
1951            f = f.with_extra_extension("wasm");
1952        } else if self.config.target.contains("spirv") {
1953            f = f.with_extra_extension("spv");
1954        } else if !env::consts::EXE_SUFFIX.is_empty() {
1955            f = f.with_extra_extension(env::consts::EXE_SUFFIX);
1956        }
1957        f
1958    }
1959
1960    fn make_run_args(&self) -> ProcArgs {
1961        // If we've got another tool to run under (valgrind),
1962        // then split apart its command
1963        let mut args = self.split_maybe_args(&self.config.runner);
1964
1965        let exe_file = self.make_exe_name();
1966
1967        args.push(exe_file.into_os_string());
1968
1969        // Add the arguments in the run_flags directive
1970        args.extend(self.props.run_flags.iter().map(OsString::from));
1971
1972        let prog = args.remove(0);
1973        ProcArgs { prog, args }
1974    }
1975
1976    fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<OsString> {
1977        match *argstr {
1978            Some(ref s) => s
1979                .split(' ')
1980                .filter_map(|s| {
1981                    if s.chars().all(|c| c.is_whitespace()) {
1982                        None
1983                    } else {
1984                        Some(OsString::from(s))
1985                    }
1986                })
1987                .collect(),
1988            None => Vec::new(),
1989        }
1990    }
1991
1992    fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
1993        use crate::util;
1994
1995        // Linux and mac don't require adjusting the library search path
1996        if cfg!(unix) {
1997            format!("{:?}", command)
1998        } else {
1999            // Build the LD_LIBRARY_PATH variable as it would be seen on the command line
2000            // for diagnostic purposes
2001            fn lib_path_cmd_prefix(path: &str) -> String {
2002                format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
2003            }
2004
2005            format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
2006        }
2007    }
2008
2009    fn dump_output(&self, print_output: bool, proc_name: &str, out: &str, err: &str) {
2010        let revision =
2011            if let Some(r) = self.variant.revision() { format!("{}.", r) } else { String::new() };
2012
2013        self.dump_output_file(out, &format!("{}out", revision));
2014        self.dump_output_file(err, &format!("{}err", revision));
2015
2016        if !print_output {
2017            return;
2018        }
2019
2020        let path = Utf8Path::new(proc_name);
2021        let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
2022            String::from_iter(
2023                path.parent()
2024                    .unwrap()
2025                    .file_name()
2026                    .into_iter()
2027                    .chain(Some("/"))
2028                    .chain(path.file_name()),
2029            )
2030        } else {
2031            path.file_name().unwrap().into()
2032        };
2033        writeln!(self.stdout, "------{proc_name} stdout------------------------------");
2034        writeln!(self.stdout, "{}", out);
2035        writeln!(self.stdout, "------{proc_name} stderr------------------------------");
2036        writeln!(self.stdout, "{}", err);
2037        writeln!(self.stdout, "------------------------------------------");
2038    }
2039
2040    fn dump_output_file(&self, out: &str, extension: &str) {
2041        let outfile = self.make_out_name(extension);
2042        fs::write(outfile.as_std_path(), out)
2043            .unwrap_or_else(|err| panic!("failed to write {outfile}: {err:?}"));
2044    }
2045
2046    /// Creates a filename for output with the given extension.
2047    /// E.g., `/.../testname.revision.mode/testname.extension`.
2048    fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
2049        self.output_base_name().with_extension(extension)
2050    }
2051
2052    /// Gets the directory where auxiliary files are written.
2053    /// E.g., `/.../testname.revision.mode/auxiliary/`.
2054    fn aux_output_dir_name(&self) -> Utf8PathBuf {
2055        self.output_base_dir()
2056            .join("auxiliary")
2057            .with_extra_extension(self.config.mode.aux_dir_disambiguator())
2058    }
2059
2060    /// Gets the directory where auxiliary binaries are written.
2061    /// E.g., `/.../testname.revision.mode/auxiliary/bin`.
2062    fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
2063        self.aux_output_dir_name().join("bin")
2064    }
2065
2066    /// The revision, ignored for incremental compilation since it wants all revisions in
2067    /// the same directory.
2068    fn variant_with_safe_revision(&self) -> TestVariant {
2069        if self.config.mode == TestMode::Incremental {
2070            TestVariant { revision: None, debugger: self.variant.debugger }
2071        } else {
2072            self.variant.clone()
2073        }
2074    }
2075
2076    /// Gets the absolute path to the directory where all output for the given
2077    /// test/revision should reside.
2078    /// E.g., `/path/to/build/host-tuple/test/ui/relative/testname.revision.mode/`.
2079    fn output_base_dir(&self) -> Utf8PathBuf {
2080        output_base_dir(self.config, self.testpaths, &self.variant_with_safe_revision())
2081    }
2082
2083    /// Gets the absolute path to the base filename used as output for the given
2084    /// test/revision.
2085    /// E.g., `/.../relative/testname.revision.mode/testname`.
2086    fn output_base_name(&self) -> Utf8PathBuf {
2087        output_base_name(self.config, self.testpaths, &self.variant_with_safe_revision())
2088    }
2089
2090    /// Prints a message to (captured) stdout if `config.verbose` is true.
2091    /// The message is also logged to `tracing::debug!` regardless of verbosity.
2092    ///
2093    /// Use `format_args!` as the argument to perform formatting if required.
2094    fn logv(&self, message: impl fmt::Display) {
2095        debug!("{message}");
2096        if self.config.verbose {
2097            // Note: `./x test ... --verbose --no-capture` is needed to see this print.
2098            writeln!(self.stdout, "{message}");
2099        }
2100    }
2101
2102    /// Prefix to print before error messages. Normally just `error`, but also
2103    /// includes the revision name for tests that use revisions.
2104    #[must_use]
2105    fn error_prefix(&self) -> String {
2106        match self.variant.revision() {
2107            Some(rev) => format!("error in revision `{rev}`"),
2108            None => format!("error"),
2109        }
2110    }
2111
2112    #[track_caller]
2113    fn fatal(&self, err: &str) -> ! {
2114        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2115        error!("fatal error, panic: {:?}", err);
2116        panic!("fatal error");
2117    }
2118
2119    fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! {
2120        self.fatal_proc_rec_general(err, None, proc_res, || ());
2121    }
2122
2123    /// Underlying implementation of [`Self::fatal_proc_rec`], providing some
2124    /// extra capabilities not needed by most callers.
2125    fn fatal_proc_rec_general(
2126        &self,
2127        err: &str,
2128        extra_note: Option<&str>,
2129        proc_res: &ProcRes,
2130        callback_before_unwind: impl FnOnce(),
2131    ) -> ! {
2132        writeln!(self.stdout, "\n{prefix}: {err}", prefix = self.error_prefix());
2133
2134        // Some callers want to print additional notes after the main error message.
2135        if let Some(note) = extra_note {
2136            writeln!(self.stdout, "{note}");
2137        }
2138
2139        // Print the details and output of the subprocess that caused this test to fail.
2140        writeln!(self.stdout, "{}", proc_res.format_info());
2141
2142        // Some callers want print more context or show a custom diff before the unwind occurs.
2143        callback_before_unwind();
2144
2145        // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from
2146        // compiletest, which is unnecessary noise.
2147        std::panic::resume_unwind(Box::new(()));
2148    }
2149
2150    // codegen tests (using FileCheck)
2151
2152    fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
2153        let output_path = self.output_base_name().with_extension("ll");
2154        let input_file = &self.testpaths.file;
2155        let rustc = self.make_compile_args(
2156            CompilerKind::Rustc,
2157            input_file,
2158            TargetLocation::ThisFile(output_path.clone()),
2159            Emit::LlvmIr,
2160            AllowUnused::No,
2161            LinkToAux::Yes,
2162            Vec::new(),
2163        );
2164
2165        let proc_res = self.compose_and_run_compiler(rustc, None);
2166        (proc_res, output_path)
2167    }
2168
2169    fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
2170        let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
2171        filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
2172
2173        // Because we use custom prefixes, we also have to register the default prefix.
2174        filecheck.arg("--check-prefix=CHECK");
2175
2176        // FIXME(#134510): auto-registering revision names as check prefix is a bit sketchy, and
2177        // that having to pass `--allow-unused-prefix` is an unfortunate side-effect of not knowing
2178        // whether the test author actually wanted revision-specific check prefixes or not.
2179        //
2180        // TL;DR We may not want to conflate `compiletest` revisions and `FileCheck` prefixes.
2181
2182        // HACK: tests are allowed to use a revision name as a check prefix.
2183        if let Some(rev) = self.variant.revision() {
2184            filecheck.arg("--check-prefix").arg(rev);
2185        }
2186
2187        // HACK: the filecheck tool normally fails if a prefix is defined but not used. However,
2188        // sometimes revisions are used to specify *compiletest* directives which are not FileCheck
2189        // concerns.
2190        filecheck.arg("--allow-unused-prefixes");
2191
2192        // Provide more context on failures.
2193        filecheck.args(&["--dump-input-context", "100"]);
2194
2195        // Add custom flags supplied by the `filecheck-flags:` test directive.
2196        filecheck.args(&self.props.filecheck_flags);
2197
2198        // FIXME(jieyouxu): don't pass an empty Path
2199        self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
2200    }
2201
2202    fn charset() -> &'static str {
2203        // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset
2204        if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
2205    }
2206
2207    fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
2208        let content = fs::read_to_string(path.as_std_path()).unwrap();
2209        let mut ignore = false;
2210        content
2211            .lines()
2212            .enumerate()
2213            .filter_map(|(line_nb, line)| {
2214                if (line.trim_start().starts_with("pub mod ")
2215                    || line.trim_start().starts_with("mod "))
2216                    && line.ends_with(';')
2217                {
2218                    if let Some(ref mut other_files) = other_files {
2219                        other_files.push(line.rsplit("mod ").next().unwrap().replace(';', ""));
2220                    }
2221                    None
2222                } else {
2223                    let sline = line.rsplit("///").next().unwrap();
2224                    let line = sline.trim_start();
2225                    if line.starts_with("```") {
2226                        if ignore {
2227                            ignore = false;
2228                            None
2229                        } else {
2230                            ignore = true;
2231                            Some(line_nb + 1)
2232                        }
2233                    } else {
2234                        None
2235                    }
2236                }
2237            })
2238            .collect()
2239    }
2240
2241    /// This method is used for `//@ check-test-line-numbers-match`.
2242    ///
2243    /// It checks that doctests line in the displayed doctest "name" matches where they are
2244    /// defined in source code.
2245    fn check_rustdoc_test_option(&self, res: ProcRes) {
2246        let mut other_files = Vec::new();
2247        let mut files: HashMap<String, Vec<usize>> = HashMap::new();
2248        let normalized = fs::canonicalize(&self.testpaths.file).expect("failed to canonicalize");
2249        let normalized = normalized.to_str().unwrap().replace('\\', "/");
2250        files.insert(normalized, self.get_lines(&self.testpaths.file, Some(&mut other_files)));
2251        for other_file in other_files {
2252            let mut path = self.testpaths.file.clone();
2253            path.set_file_name(&format!("{}.rs", other_file));
2254            let path = path.canonicalize_utf8().expect("failed to canonicalize");
2255            let normalized = path.as_str().replace('\\', "/");
2256            files.insert(normalized, self.get_lines(&path, None));
2257        }
2258
2259        let mut tested = 0;
2260        for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| {
2261            if let Some((left, right)) = s.split_once(" - ") {
2262                let path = left.rsplit("test ").next().unwrap();
2263                let path = fs::canonicalize(&path).expect("failed to canonicalize");
2264                let path = path.to_str().unwrap().replace('\\', "/");
2265                if let Some(ref mut v) = files.get_mut(&path) {
2266                    tested += 1;
2267                    let mut iter = right.split("(line ");
2268                    iter.next();
2269                    let line = iter
2270                        .next()
2271                        .unwrap_or(")")
2272                        .split(')')
2273                        .next()
2274                        .unwrap_or("0")
2275                        .parse()
2276                        .unwrap_or(0);
2277                    if let Ok(pos) = v.binary_search(&line) {
2278                        v.remove(pos);
2279                    } else {
2280                        self.fatal_proc_rec(
2281                            &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v),
2282                            &res,
2283                        );
2284                    }
2285                }
2286            }
2287        }) {}
2288        if tested == 0 {
2289            self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res);
2290        } else {
2291            for (entry, v) in &files {
2292                if !v.is_empty() {
2293                    self.fatal_proc_rec(
2294                        &format!(
2295                            "Not found test at line{} \"{}\":{:?}",
2296                            if v.len() > 1 { "s" } else { "" },
2297                            entry,
2298                            v
2299                        ),
2300                        &res,
2301                    );
2302                }
2303            }
2304        }
2305    }
2306
2307    fn force_color_svg(&self) -> bool {
2308        self.props.compile_flags.iter().any(|s| s.contains("--color=always"))
2309    }
2310
2311    /// Returns the lines for the by-lines comparison, normalized for the
2312    /// parallel front-end: for SVG output, strip the header line and `y`
2313    /// offsets; otherwise, filter out padded empty code lines (a single `|`).
2314    fn lines_for_comparison(&self, output: &str) -> Vec<String> {
2315        if self.force_color_svg() {
2316            let strip_y = static_regex!(r#"y="\d+px""#);
2317            output
2318                .lines()
2319                // anstyle_svg causes environment-dependent width parameter
2320                .skip(1)
2321                .map(|line| strip_y.replace_all(line, r#"y="0px""#).into_owned())
2322                .collect()
2323        } else {
2324            output.lines().filter(|l| l.trim() != "|").map(str::to_owned).collect()
2325        }
2326    }
2327
2328    fn load_compare_outputs(
2329        &self,
2330        proc_res: &ProcRes,
2331        output_kind: TestOutput,
2332        explicit_format: bool,
2333    ) -> usize {
2334        let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width());
2335        let (stderr_kind, stdout_kind) = match output_kind {
2336            TestOutput::Compile => (
2337                if self.force_color_svg() {
2338                    if self.config.target.contains("windows") {
2339                        // We single out Windows here because some of the CLI coloring is
2340                        // specifically changed for Windows.
2341                        UI_WINDOWS_SVG
2342                    } else {
2343                        UI_SVG
2344                    }
2345                } else if self.props.stderr_per_bitwidth {
2346                    &stderr_bits
2347                } else {
2348                    UI_STDERR
2349                },
2350                UI_STDOUT,
2351            ),
2352            TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT),
2353        };
2354
2355        let expected_stderr = self.load_expected_output(stderr_kind);
2356        let expected_stdout = self.load_expected_output(stdout_kind);
2357
2358        let mut normalized_stdout =
2359            self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout);
2360        match output_kind {
2361            TestOutput::Run if self.config.remote_test_client.is_some() => {
2362                // When tests are run using the remote-test-client, the string
2363                // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"'
2364                // is printed to stdout by the client and then captured in the ProcRes,
2365                // so it needs to be removed when comparing the run-pass test execution output.
2366                normalized_stdout = static_regex!(
2367                    "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
2368                )
2369                .replace(&normalized_stdout, "")
2370                .to_string();
2371                // When there is a panic, the remote-test-client also prints "died due to signal";
2372                // that needs to be removed as well.
2373                normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
2374                    .replace(&normalized_stdout, "")
2375                    .to_string();
2376                // FIXME: it would be much nicer if we could just tell the remote-test-client to not
2377                // print these things.
2378            }
2379            _ => {}
2380        };
2381
2382        let stderr;
2383        let normalized_stderr;
2384
2385        if self.force_color_svg() {
2386            let normalized = self.normalize_output(&proc_res.stderr, &self.props.normalize_stderr);
2387            stderr = anstyle_svg::Term::new().render_svg(&normalized);
2388            normalized_stderr = stderr.clone();
2389        } else {
2390            stderr = if explicit_format {
2391                proc_res.stderr.clone()
2392            } else {
2393                json::extract_rendered(&proc_res.stderr)
2394            };
2395            normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr);
2396        }
2397
2398        let mut errors = 0;
2399        match output_kind {
2400            TestOutput::Compile => {
2401                if !self.props.dont_check_compiler_stdout {
2402                    if self
2403                        .compare_output(
2404                            stdout_kind,
2405                            &normalized_stdout,
2406                            &proc_res.stdout,
2407                            &expected_stdout,
2408                        )
2409                        .should_error()
2410                    {
2411                        errors += 1;
2412                    }
2413                }
2414                if !self.props.dont_check_compiler_stderr {
2415                    if self
2416                        .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2417                        .should_error()
2418                    {
2419                        errors += 1;
2420                    }
2421                }
2422            }
2423            TestOutput::Run => {
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
2437                    .compare_output(stderr_kind, &normalized_stderr, &stderr, &expected_stderr)
2438                    .should_error()
2439                {
2440                    errors += 1;
2441                }
2442            }
2443        }
2444        errors
2445    }
2446
2447    fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String {
2448        // Crude heuristic to detect when the output should have JSON-specific
2449        // normalization steps applied.
2450        let rflags = self.props.run_flags.join(" ");
2451        let cflags = self.props.compile_flags.join(" ");
2452        let json = rflags.contains("--format json")
2453            || rflags.contains("--format=json")
2454            || cflags.contains("--error-format json")
2455            || cflags.contains("--error-format pretty-json")
2456            || cflags.contains("--error-format=json")
2457            || cflags.contains("--error-format=pretty-json")
2458            || cflags.contains("--output-format json")
2459            || cflags.contains("--output-format=json");
2460
2461        let mut normalized = output.to_string();
2462
2463        let mut normalize_path = |from: &Utf8Path, to: &str| {
2464            let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
2465
2466            normalized = normalized.replace(from, to);
2467        };
2468
2469        let parent_dir = self.testpaths.file.parent().unwrap();
2470        normalize_path(parent_dir, "$DIR");
2471
2472        if self.props.remap_src_base {
2473            let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
2474            if self.testpaths.relative_dir != Utf8Path::new("") {
2475                remapped_parent_dir.push(&self.testpaths.relative_dir);
2476            }
2477            normalize_path(&remapped_parent_dir, "$DIR");
2478        }
2479
2480        let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
2481        // Fake paths into the libstd/libcore
2482        normalize_path(&base_dir.join("library"), "$SRC_DIR");
2483        // `ui-fulldeps` tests can show paths to the compiler source when testing macros from
2484        // `rustc_macros`
2485        // eg. /home/user/rust/compiler
2486        normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR");
2487
2488        // Real paths into the libstd/libcore
2489        let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
2490        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
2491        let rust_src_dir =
2492            rust_src_dir.read_link_utf8().unwrap_or_else(|_| rust_src_dir.to_path_buf());
2493        normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
2494
2495        // Real paths into the compiler
2496        let rustc_src_dir = &self.config.sysroot_base.join("lib/rustlib/rustc-src/rust");
2497        rustc_src_dir.try_exists().expect(&*format!("{} should exists", rustc_src_dir));
2498        let rustc_src_dir = rustc_src_dir.read_link_utf8().unwrap_or(rustc_src_dir.to_path_buf());
2499        normalize_path(&rustc_src_dir.join("compiler"), "$COMPILER_DIR_REAL");
2500
2501        // eg.
2502        // /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
2503        normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
2504        // Same as above, but with a canonicalized path.
2505        // This is required because some tests print canonical paths inside test build directory,
2506        // so if the build directory is a symlink, normalization doesn't help.
2507        //
2508        // NOTE: There are also tests which print the non-canonical name, so we need both this and
2509        // the above normalizations.
2510        normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
2511        // eg. /home/user/rust/build
2512        normalize_path(&self.config.build_root, "$BUILD_DIR");
2513
2514        if json {
2515            // escaped newlines in json strings should be readable
2516            // in the stderr files. There's no point in being correct,
2517            // since only humans process the stderr files.
2518            // Thus we just turn escaped newlines back into newlines.
2519            normalized = normalized.replace("\\n", "\n");
2520        }
2521
2522        // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them
2523        // with placeholders as we do not want tests needing updated when compiler source code
2524        // changes.
2525        // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL
2526        normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
2527            .replace_all(&normalized, "SRC_DIR$1:LL:COL")
2528            .into_owned();
2529
2530        normalized = Self::normalize_platform_differences(&normalized);
2531
2532        // Normalize long type name hash.
2533        normalized =
2534            static_regex!(r"\$TEST_BUILD_DIR/(?P<filename>[^\.]+).long-type-(?P<hash>\d+).txt")
2535                .replace_all(&normalized, |caps: &Captures<'_>| {
2536                    format!(
2537                        "$TEST_BUILD_DIR/{filename}.long-type-$LONG_TYPE_HASH.txt",
2538                        filename = &caps["filename"]
2539                    )
2540                })
2541                .into_owned();
2542
2543        // Normalize thread IDs in panic messages
2544        normalized = static_regex!(r"thread '(?P<name>.*?)' \((rtid )?\d+\) panicked")
2545            .replace_all(&normalized, "thread '$name' ($$TID) panicked")
2546            .into_owned();
2547
2548        normalized = normalized.replace("\t", "\\t"); // makes tabs visible
2549
2550        // Remove test annotations like `//~ ERROR text` from the output,
2551        // since they duplicate actual errors and make the output hard to read.
2552        // This mirrors the regex in src/tools/tidy/src/style.rs, please update
2553        // both if either are changed.
2554        normalized =
2555            static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
2556
2557        // This code normalizes various hashes in v0 symbol mangling that is
2558        // emitted in the ui and mir-opt tests.
2559        let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
2560        let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
2561
2562        const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
2563        if v0_crate_hash_prefix_re.is_match(&normalized) {
2564            // Normalize crate hash
2565            normalized =
2566                v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
2567        }
2568
2569        let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
2570        let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
2571
2572        const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
2573        if v0_back_ref_prefix_re.is_match(&normalized) {
2574            // Normalize back references (see RFC 2603)
2575            normalized =
2576                v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
2577        }
2578
2579        // AllocId are numbered globally in a compilation session. This can lead to changes
2580        // depending on the exact compilation flags and host architecture. Meanwhile, we want
2581        // to keep them numbered, to see if the same id appears multiple times.
2582        // So we remap to deterministic numbers that only depend on the subset of allocations
2583        // that actually appear in the output.
2584        // We use uppercase ALLOC to distinguish from the non-normalized version.
2585        {
2586            match self.config.mode {
2587                // Unfortunately, due to parallel frontend assigning alloc-ids
2588                // nondeterministically we resort to dropping ids altogether for now
2589                // in ui tests
2590                TestMode::Ui => {
2591                    // The alloc-id appears in pretty-printed allocations.
2592                    normalized = static_regex!(
2593                        r"╾─*(a(lloc)?|A(LLOC)?)\d+(\+0x[0-9a-f]+)?(<imm>)?( ?\(\d+ ptr bytes\))?─*╼"
2594                    )
2595                    .replace_all(&normalized, |_: &Captures<'_>| "╾ALLOC$ID╼".to_string())
2596                    .into_owned();
2597
2598                    // The alloc-id appears in a sentence.
2599                    normalized = static_regex!(r"\b(alloc|ALLOC)\d+\b")
2600                        .replace_all(&normalized, |_: &Captures<'_>| "ALLOC$ID".to_string())
2601                        .into_owned();
2602                }
2603                // use consistent `AllocId`s in other test modes, where parallel frontend
2604                // should not (theoretically) be an issue
2605                _ => {
2606                    let mut seen_allocs = indexmap::IndexSet::new();
2607                    // The alloc-id appears in pretty-printed allocations.
2608                    normalized = static_regex!(
2609                        r"╾─*a(lloc)?([0-9]+)(\+0x[0-9a-f]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
2610                    )
2611                    .replace_all(&normalized, |caps: &Captures<'_>| {
2612                        // Renumber the captured index.
2613                        let index = caps.get(2).unwrap().as_str().to_string();
2614                        let (index, _) = seen_allocs.insert_full(index);
2615                        let offset = caps.get(3).map_or("", |c| c.as_str());
2616                        let imm = caps.get(4).map_or("", |c| c.as_str());
2617                        // Do not bother keeping it pretty, just make it deterministic.
2618                        format!("╾ALLOC{index}{offset}{imm}╼")
2619                    })
2620                    .into_owned();
2621
2622                    // The alloc-id appears in a sentence.
2623                    normalized = static_regex!(r"\balloc([0-9]+)\b")
2624                        .replace_all(&normalized, |caps: &Captures<'_>| {
2625                            let index = caps.get(1).unwrap().as_str().to_string();
2626                            let (index, _) = seen_allocs.insert_full(index);
2627                            format!("ALLOC{index}")
2628                        })
2629                        .into_owned();
2630                }
2631            }
2632        }
2633
2634        // Custom normalization rules
2635        for rule in custom_rules {
2636            let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule");
2637            normalized = re.replace_all(&normalized, &rule.1[..]).into_owned();
2638        }
2639        normalized
2640    }
2641
2642    /// Normalize output differences across platforms. Generally changes Windows output to be more
2643    /// Unix-like.
2644    ///
2645    /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings
2646    /// with LF.
2647    fn normalize_platform_differences(output: &str) -> String {
2648        let output = output.replace(r"\\", r"\");
2649
2650        // Used to find Windows paths.
2651        //
2652        // It's not possible to detect paths in the error messages generally, but this is a
2653        // decent enough heuristic.
2654        let re = static_regex!(
2655            r#"(?x)
2656                (?:
2657                  # Match paths that don't include spaces.
2658                  (?:\\[\pL\pN\.\-_']+)+\.\pL+
2659                |
2660                  # If the path starts with a well-known root, then allow spaces and no file extension.
2661                  \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
2662                )"#
2663        );
2664        re.replace_all(&output, |caps: &Captures<'_>| caps[0].replace(r"\", "/"))
2665            .replace("\r\n", "\n")
2666    }
2667
2668    fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
2669        let mut path = expected_output_path(
2670            &self.testpaths,
2671            self.variant.revision(),
2672            &self.config.compare_mode,
2673            kind,
2674        );
2675
2676        if !path.exists() {
2677            if let Some(CompareMode::Polonius) = self.config.compare_mode {
2678                path = expected_output_path(&self.testpaths, self.variant.revision(), &None, kind);
2679            }
2680        }
2681
2682        if !path.exists() {
2683            path = expected_output_path(&self.testpaths, self.variant.revision(), &None, kind);
2684        }
2685
2686        path
2687    }
2688
2689    fn load_expected_output(&self, kind: &str) -> String {
2690        let path = self.expected_output_path(kind);
2691        if path.exists() {
2692            match self.load_expected_output_from_path(&path) {
2693                Ok(x) => x,
2694                Err(x) => self.fatal(&x),
2695            }
2696        } else {
2697            String::new()
2698        }
2699    }
2700
2701    fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
2702        fs::read_to_string(path)
2703            .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
2704    }
2705
2706    /// Attempts to delete a file, succeeding if the file does not exist.
2707    fn delete_file(&self, file: &Utf8Path) {
2708        if let Err(e) = fs::remove_file(file.as_std_path())
2709            && e.kind() != io::ErrorKind::NotFound
2710        {
2711            self.fatal(&format!("failed to delete `{}`: {}", file, e,));
2712        }
2713    }
2714
2715    fn compare_output(
2716        &self,
2717        stream: &str,
2718        actual: &str,
2719        actual_unnormalized: &str,
2720        expected: &str,
2721    ) -> CompareOutcome {
2722        let expected_path = expected_output_path(
2723            self.testpaths,
2724            self.variant.revision(),
2725            &self.config.compare_mode,
2726            stream,
2727        );
2728
2729        if self.config.bless && actual.is_empty() && expected_path.exists() {
2730            self.delete_file(&expected_path);
2731        }
2732
2733        let are_different = match (self.force_color_svg(), expected.find('\n'), actual.find('\n')) {
2734            // FIXME: We ignore the first line of SVG files
2735            // because the width parameter is non-deterministic.
2736            (true, Some(nl_e), Some(nl_a)) => expected[nl_e..] != actual[nl_a..],
2737            _ => expected != actual,
2738        };
2739        if !are_different {
2740            return CompareOutcome::Same;
2741        }
2742
2743        // Wrapper tools set by `runner` might provide extra output on failure,
2744        // for example a WebAssembly runtime might print the stack trace of an
2745        // `unreachable` instruction by default.
2746        let compare_output_by_lines_subset = self.config.runner.is_some();
2747
2748        // Also, some tests like `ui/parallel-rustc` have non-deterministic
2749        // orders of output, so we need to compare by lines.
2750        let compare_output_by_lines = self.props.compare_output_by_lines;
2751
2752        let tmp;
2753        let (expected, actual): (&str, &str) = if compare_output_by_lines_subset {
2754            let actual_lines: HashSet<_> = actual.lines().collect();
2755            let expected_lines: Vec<_> = expected.lines().collect();
2756            let mut used = expected_lines.clone();
2757            used.retain(|line| actual_lines.contains(line));
2758
2759            // check if `expected` contains a subset of the lines of `actual`
2760            if used.len() == expected_lines.len() && (expected.is_empty() == actual.is_empty()) {
2761                return CompareOutcome::Same;
2762            }
2763            if expected_lines.is_empty() {
2764                // if we have no lines to check, force a full overwrite
2765                ("", actual)
2766            } else {
2767                // this prints/blesses the subset, not the actual
2768                tmp = (expected_lines.join("\n"), used.join("\n"));
2769                (&tmp.0, &tmp.1)
2770            }
2771        } else if compare_output_by_lines {
2772            let mut actual_lines = self.lines_for_comparison(actual);
2773            let mut expected_lines = self.lines_for_comparison(expected);
2774            actual_lines.sort_unstable();
2775            expected_lines.sort_unstable();
2776            if actual_lines == expected_lines {
2777                return CompareOutcome::Same;
2778            } else {
2779                (expected, actual)
2780            }
2781        } else {
2782            (expected, actual)
2783        };
2784
2785        // Write the actual output to a file in build directory.
2786        let actual_path = self
2787            .output_base_name()
2788            .with_extra_extension(self.variant.revision().unwrap_or(""))
2789            .with_extra_extension(
2790                self.config.compare_mode.as_ref().map(|cm| cm.to_str()).unwrap_or(""),
2791            )
2792            .with_extra_extension(stream);
2793
2794        if let Err(err) = fs::write(&actual_path, &actual) {
2795            self.fatal(&format!("failed to write {stream} to `{actual_path}`: {err}",));
2796        }
2797        writeln!(self.stdout, "Saved the actual {stream} to `{actual_path}`");
2798
2799        if !self.config.bless {
2800            if expected.is_empty() {
2801                writeln!(self.stdout, "normalized {}:\n{}\n", stream, actual);
2802            } else {
2803                self.show_diff(
2804                    stream,
2805                    &expected_path,
2806                    &actual_path,
2807                    expected,
2808                    actual,
2809                    actual_unnormalized,
2810                    compare_output_by_lines || compare_output_by_lines_subset,
2811                );
2812            }
2813        } else {
2814            // Delete non-revision .stderr/.stdout file if revisions are used.
2815            // Without this, we'd just generate the new files and leave the old files around.
2816            if self.variant.revision().is_some() {
2817                let old =
2818                    expected_output_path(self.testpaths, None, &self.config.compare_mode, stream);
2819                self.delete_file(&old);
2820            }
2821
2822            if !actual.is_empty() {
2823                if let Err(err) = fs::write(&expected_path, &actual) {
2824                    self.fatal(&format!("failed to write {stream} to `{expected_path}`: {err}"));
2825                }
2826                writeln!(
2827                    self.stdout,
2828                    "Blessing the {stream} of `{test_name}` as `{expected_path}`",
2829                    test_name = self.testpaths.file
2830                );
2831            }
2832        }
2833
2834        writeln!(self.stdout, "\nThe actual {stream} differed from the expected {stream}");
2835
2836        if self.config.bless { CompareOutcome::Blessed } else { CompareOutcome::Differed }
2837    }
2838
2839    /// Returns whether to show the full stderr/stdout.
2840    fn show_diff(
2841        &self,
2842        stream: &str,
2843        expected_path: &Utf8Path,
2844        actual_path: &Utf8Path,
2845        expected: &str,
2846        actual: &str,
2847        actual_unnormalized: &str,
2848        show_diff_by_lines: bool,
2849    ) {
2850        writeln!(self.stderr, "diff of {stream}:\n");
2851        if let Some(diff_command) = self.config.diff_command.as_deref() {
2852            let mut args = diff_command.split_whitespace();
2853            let name = args.next().unwrap();
2854            match Command::new(name).args(args).args([expected_path, actual_path]).output() {
2855                Err(err) => {
2856                    self.fatal(&format!(
2857                        "failed to call custom diff command `{diff_command}`: {err}"
2858                    ));
2859                }
2860                Ok(output) => {
2861                    let output = String::from_utf8_lossy(&output.stdout);
2862                    write!(self.stderr, "{output}");
2863                }
2864            }
2865        } else {
2866            write!(self.stderr, "{}", write_diff(expected, actual, 3));
2867        }
2868
2869        // 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.
2870        let diff_results = make_diff(actual, expected, 0);
2871
2872        let (mut mismatches_normalized, mut mismatch_line_nos) = (String::new(), vec![]);
2873        for hunk in diff_results {
2874            let mut line_no = hunk.line_number;
2875            for line in hunk.lines {
2876                // NOTE: `Expected` is actually correct here, the argument order is reversed so our line numbers match up
2877                if let DiffLine::Expected(normalized) = line {
2878                    mismatches_normalized += &normalized;
2879                    mismatches_normalized += "\n";
2880                    mismatch_line_nos.push(line_no);
2881                    line_no += 1;
2882                }
2883            }
2884        }
2885        let mut mismatches_unnormalized = String::new();
2886        let diff_normalized = make_diff(actual, actual_unnormalized, 0);
2887        for hunk in diff_normalized {
2888            if mismatch_line_nos.contains(&hunk.line_number) {
2889                for line in hunk.lines {
2890                    if let DiffLine::Resulting(unnormalized) = line {
2891                        mismatches_unnormalized += &unnormalized;
2892                        mismatches_unnormalized += "\n";
2893                    }
2894                }
2895            }
2896        }
2897
2898        let normalized_diff = make_diff(&mismatches_normalized, &mismatches_unnormalized, 0);
2899        // 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.
2900        if !normalized_diff.is_empty()
2901            && !mismatches_unnormalized.is_empty()
2902            && !mismatches_normalized.is_empty()
2903        {
2904            writeln!(
2905                self.stderr,
2906                "Note: some mismatched output was normalized before being compared"
2907            );
2908            // FIXME: respect diff_command
2909            write!(
2910                self.stderr,
2911                "{}",
2912                write_diff(&mismatches_unnormalized, &mismatches_normalized, 0)
2913            );
2914        }
2915
2916        if show_diff_by_lines {
2917            let expected_lines = self.lines_for_comparison(expected);
2918            let actual_lines = self.lines_for_comparison(actual);
2919            write!(self.stderr, "{}", diff_by_lines(&expected_lines, &actual_lines));
2920        }
2921    }
2922
2923    fn check_and_prune_duplicate_outputs(
2924        &self,
2925        proc_res: &ProcRes,
2926        modes: &[CompareMode],
2927        require_same_modes: &[CompareMode],
2928    ) {
2929        for kind in UI_EXTENSIONS {
2930            let canon_comparison_path =
2931                expected_output_path(&self.testpaths, self.variant.revision(), &None, kind);
2932
2933            let canon = match self.load_expected_output_from_path(&canon_comparison_path) {
2934                Ok(canon) => canon,
2935                _ => continue,
2936            };
2937            let bless = self.config.bless;
2938            let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| {
2939                let examined_path = expected_output_path(
2940                    &self.testpaths,
2941                    self.variant.revision(),
2942                    &Some(mode.clone()),
2943                    kind,
2944                );
2945
2946                // If there is no output, there is nothing to do
2947                let examined_content = match self.load_expected_output_from_path(&examined_path) {
2948                    Ok(content) => content,
2949                    _ => return,
2950                };
2951
2952                let is_duplicate = canon == examined_content;
2953
2954                match (bless, require_same, is_duplicate) {
2955                    // If we're blessing and the output is the same, then delete the file.
2956                    (true, _, true) => {
2957                        self.delete_file(&examined_path);
2958                    }
2959                    // If we want them to be the same, but they are different, then error.
2960                    // We do this whether we bless or not
2961                    (_, true, false) => {
2962                        self.fatal_proc_rec(
2963                            &format!("`{}` should not have different output from base test!", kind),
2964                            proc_res,
2965                        );
2966                    }
2967                    _ => {}
2968                }
2969            };
2970            for mode in modes {
2971                check_and_prune_duplicate_outputs(mode, false);
2972            }
2973            for mode in require_same_modes {
2974                check_and_prune_duplicate_outputs(mode, true);
2975            }
2976        }
2977    }
2978
2979    fn create_stamp(&self) {
2980        let stamp_file_path = stamp_file_path(&self.config, self.testpaths, self.variant);
2981        fs::write(&stamp_file_path, compute_stamp_hash(&self.config, self.variant)).unwrap();
2982    }
2983
2984    fn init_incremental_test(&self) {
2985        // (See `run_incremental_test` for an overview of how incremental tests work.)
2986
2987        // Before any of the revisions have executed, create the
2988        // incremental workproduct directory.  Delete any old
2989        // incremental work products that may be there from prior
2990        // runs.
2991        let incremental_dir = self.props.incremental_dir.as_ref().unwrap();
2992        if incremental_dir.exists() {
2993            // Canonicalizing the path will convert it to the //?/ format
2994            // on Windows, which enables paths longer than 260 character
2995            let canonicalized = incremental_dir.canonicalize().unwrap();
2996            fs::remove_dir_all(canonicalized).unwrap();
2997        }
2998        fs::create_dir_all(&incremental_dir).unwrap();
2999
3000        if self.config.verbose {
3001            writeln!(self.stdout, "init_incremental_test: incremental_dir={incremental_dir}");
3002        }
3003    }
3004}
3005
3006struct ProcArgs {
3007    prog: OsString,
3008    args: Vec<OsString>,
3009}
3010
3011#[derive(Debug)]
3012pub(crate) struct ProcRes {
3013    status: ExitStatus,
3014    stdout: String,
3015    stderr: String,
3016    truncated: Truncated,
3017    cmdline: String,
3018}
3019
3020impl ProcRes {
3021    #[must_use]
3022    pub(crate) fn format_info(&self) -> String {
3023        fn render(name: &str, contents: &str) -> String {
3024            let contents = json::extract_rendered(contents);
3025            let contents = contents.trim_end();
3026            if contents.is_empty() {
3027                format!("{name}: none")
3028            } else {
3029                format!(
3030                    "\
3031                     --- {name} -------------------------------\n\
3032                     {contents}\n\
3033                     ------------------------------------------",
3034                )
3035            }
3036        }
3037
3038        format!(
3039            "status: {}\ncommand: {}\n{}\n{}\n",
3040            self.status,
3041            self.cmdline,
3042            render("stdout", &self.stdout),
3043            render("stderr", &self.stderr),
3044        )
3045    }
3046}
3047
3048#[derive(Debug)]
3049enum TargetLocation {
3050    ThisFile(Utf8PathBuf),
3051    ThisDirectory(Utf8PathBuf),
3052}
3053
3054enum AllowUnused {
3055    Yes,
3056    No,
3057}
3058
3059enum LinkToAux {
3060    Yes,
3061    No,
3062}
3063
3064#[derive(Debug, PartialEq)]
3065enum AuxType {
3066    Bin,
3067    Lib,
3068    Dylib,
3069    ProcMacro,
3070}
3071
3072/// Outcome of comparing a stream to a blessed file,
3073/// e.g. `.stderr` and `.fixed`.
3074#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3075enum CompareOutcome {
3076    /// Expected and actual outputs are the same
3077    Same,
3078    /// Outputs differed but were blessed
3079    Blessed,
3080    /// Outputs differed and an error should be emitted
3081    Differed,
3082}
3083
3084#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3085enum DocKind {
3086    Html,
3087    Json,
3088}
3089
3090impl CompareOutcome {
3091    fn should_error(&self) -> bool {
3092        matches!(self, CompareOutcome::Differed)
3093    }
3094}