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