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