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