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