Skip to main content

compiletest/
runtest.rs

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