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