compiletest/
runtest.rs

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