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