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