Skip to main content

compiletest/
runtest.rs

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