compiletest/
runtest.rs

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