compiletest/
runtest.rs

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