compiletest/
runtest.rs

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