compiletest/
runtest.rs

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