Skip to main content

compiletest/runtest/
ui.rs

1use std::collections::HashSet;
2use std::fs::OpenOptions;
3use std::io::Write;
4
5use rustfix::{Filter, apply_suggestions, get_suggestions_from_json};
6use tracing::debug;
7
8use crate::json;
9use crate::runtest::{
10    AllowUnused, Emit, FailMode, LinkToAux, PassMode, ProcRes, RunFailMode, RunResult,
11    TargetLocation, TestCx, TestOutput, Truncated, UI_FIXED, WillExecute,
12};
13
14impl TestCx<'_> {
15    pub(super) fn run_ui_test(&self) {
16        if let Some(FailMode::Build) = self.props.fail_mode {
17            // Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck).
18            let proc_res = self.compile_test(WillExecute::No, Emit::Metadata);
19            self.check_if_test_should_compile(
20                self.props.fail_mode,
21                Some(PassMode::Check),
22                &proc_res,
23            );
24        }
25
26        let pm = self.pass_mode();
27        let should_run = self.should_run(pm);
28        let emit_metadata = self.should_emit_metadata(pm);
29        let proc_res = self.compile_test(should_run, emit_metadata);
30        self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res);
31        if matches!(proc_res.truncated, Truncated::Yes)
32            && !self.props.dont_check_compiler_stdout
33            && !self.props.dont_check_compiler_stderr
34        {
35            self.fatal_proc_rec(
36                "compiler output got truncated, cannot compare with reference file",
37                &proc_res,
38            );
39        }
40
41        // if the user specified a format in the ui test
42        // print the output to the stderr file, otherwise extract
43        // the rendered error messages from json and print them
44        let explicit = self.props.compile_flags.iter().any(|s| s.contains("--error-format"));
45
46        let expected_fixed = self.load_expected_output(UI_FIXED);
47
48        self.check_and_prune_duplicate_outputs(&proc_res, &[], &[]);
49
50        let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit);
51        let rustfix_input = json::rustfix_diagnostics_only(&proc_res.stderr);
52
53        if self.config.compare_mode.is_some() {
54            // don't test rustfix with nll right now
55        } else if self.config.rustfix_coverage {
56            // Find out which tests have `MachineApplicable` suggestions but are missing
57            // `run-rustfix` or `run-rustfix-only-machine-applicable` directives.
58            //
59            // This will return an empty `Vec` in case the executed test file has a
60            // `compile-flags: --error-format=xxxx` directive with a value other than `json`.
61            let suggestions = get_suggestions_from_json(
62                &rustfix_input,
63                &HashSet::new(),
64                Filter::MachineApplicableOnly,
65            )
66            .unwrap_or_default();
67            if !suggestions.is_empty()
68                && !self.props.run_rustfix
69                && !self.props.rustfix_only_machine_applicable
70            {
71                let mut coverage_file_path = self.config.build_test_suite_root.clone();
72                coverage_file_path.push("rustfix_missing_coverage.txt");
73                debug!("coverage_file_path: {}", coverage_file_path);
74
75                let mut file = OpenOptions::new()
76                    .create(true)
77                    .append(true)
78                    .open(coverage_file_path.as_path())
79                    .expect("could not create or open file");
80
81                if let Err(e) = writeln!(file, "{}", self.testpaths.file) {
82                    panic!("couldn't write to {}: {e:?}", coverage_file_path);
83                }
84            }
85        } else if self.props.run_rustfix {
86            // Apply suggestions from rustc to the code itself
87            let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file).unwrap();
88            let suggestions = get_suggestions_from_json(
89                &rustfix_input,
90                &HashSet::new(),
91                if self.props.rustfix_only_machine_applicable {
92                    Filter::MachineApplicableOnly
93                } else {
94                    Filter::Everything
95                },
96            )
97            .unwrap();
98            let fixed_code = apply_suggestions(&unfixed_code, &suggestions).unwrap_or_else(|e| {
99                panic!(
100                    "failed to apply suggestions for {:?} with rustfix: {}",
101                    self.testpaths.file, e
102                )
103            });
104
105            if self
106                .compare_output("fixed", &fixed_code, &fixed_code, &expected_fixed)
107                .should_error()
108            {
109                errors += 1;
110            }
111        } else if !expected_fixed.is_empty() {
112            panic!(
113                "the `//@ run-rustfix` directive wasn't found but a `*.fixed` \
114                 file was found"
115            );
116        }
117
118        if errors > 0 {
119            writeln!(
120                self.stdout,
121                "To update references, rerun the tests and pass the `--bless` flag"
122            );
123            let relative_path_to_file =
124                self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
125            writeln!(
126                self.stdout,
127                "To only update this specific test, also pass `--test-args {}`",
128                relative_path_to_file,
129            );
130            self.fatal_proc_rec(
131                &format!("{} errors occurred comparing output.", errors),
132                &proc_res,
133            );
134        }
135
136        // If the test is executed, capture its ProcRes separately so that
137        // pattern/forbid checks can report the *runtime* stdout/stderr when they fail.
138        let mut run_proc_res: Option<ProcRes> = None;
139        let output_to_check = if let WillExecute::Yes = should_run {
140            let proc_res = self.exec_compiled_test();
141            let run_output_errors = if self.props.check_run_results {
142                self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
143            } else {
144                0
145            };
146            if run_output_errors > 0 {
147                self.fatal_proc_rec(
148                    &format!("{} errors occurred comparing run output.", run_output_errors),
149                    &proc_res,
150                );
151            }
152            let code = proc_res.status.code();
153            let run_result = if proc_res.status.success() {
154                RunResult::Pass
155            } else if code.is_some_and(|c| c >= 1 && c <= 127) {
156                RunResult::Fail
157            } else {
158                RunResult::Crash
159            };
160            // Help users understand why the test failed by including the actual
161            // exit code and actual run result in the failure message.
162            let pass_hint = format!("code={code:?} so test would pass with `{run_result}`");
163            if self.should_run_successfully(pm) {
164                if run_result != RunResult::Pass {
165                    self.fatal_proc_rec(
166                        &format!("test did not exit with success! {pass_hint}"),
167                        &proc_res,
168                    );
169                }
170            } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Fail)) {
171                // If the test is marked as `run-fail` but do not support
172                // unwinding we allow it to crash, since a panic will trigger an
173                // abort (crash) instead of unwind (exit with code 101).
174                let crash_ok = !self.config.can_unwind();
175                if run_result != RunResult::Fail && !(crash_ok && run_result == RunResult::Crash) {
176                    let err = if crash_ok {
177                        format!(
178                            "test did not exit with failure or crash (`{}` can't unwind)! {pass_hint}",
179                            self.config.target
180                        )
181                    } else {
182                        format!("test did not exit with failure! {pass_hint}")
183                    };
184                    self.fatal_proc_rec(&err, &proc_res);
185                }
186            } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Crash)) {
187                if run_result != RunResult::Crash {
188                    self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res);
189                }
190            } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::FailOrCrash)) {
191                if run_result != RunResult::Fail && run_result != RunResult::Crash {
192                    self.fatal_proc_rec(
193                        &format!("test did not exit with failure or crash! {pass_hint}"),
194                        &proc_res,
195                    );
196                }
197            } else {
198                unreachable!("run_ui_test() must not be called if the test should not run");
199            }
200
201            let output = self.get_output(&proc_res);
202            // Move the proc_res into our option after we've extracted output.
203            run_proc_res = Some(proc_res);
204            output
205        } else {
206            self.get_output(&proc_res)
207        };
208
209        debug!(
210            "run_ui_test: explicit={:?} config.compare_mode={:?} \
211               proc_res.status={:?} props.error_patterns={:?} output_to_check={:?}",
212            explicit,
213            self.config.compare_mode,
214            proc_res.status,
215            self.props.error_patterns,
216            output_to_check,
217        );
218
219        // Compiler diagnostics (expected errors) are always tied to the compile-time ProcRes.
220        self.check_expected_errors(&proc_res);
221
222        // For runtime pattern/forbid checks prefer the executed program's ProcRes if available
223        // so that missing pattern failures include the program's stdout/stderr.
224        let pattern_proc_res = run_proc_res.as_ref().unwrap_or(&proc_res);
225        self.check_all_error_patterns(&output_to_check, pattern_proc_res);
226        self.check_forbid_output(&output_to_check, pattern_proc_res);
227
228        if self.props.run_rustfix && self.config.compare_mode.is_none() {
229            // And finally, compile the fixed code and make sure it both
230            // succeeds and has no diagnostics.
231            let mut rustc = self.make_compile_args(
232                self.compiler_kind_for_non_aux(),
233                &self.expected_output_path(UI_FIXED),
234                TargetLocation::ThisFile(self.make_exe_name()),
235                emit_metadata,
236                AllowUnused::No,
237                LinkToAux::Yes,
238                Vec::new(),
239            );
240
241            // If a test is revisioned, it's fixed source file can be named "a.foo.fixed", which,
242            // well, "a.foo" isn't a valid crate name. So we explicitly mangle the test name
243            // (including the revision) here to avoid the test writer having to manually specify a
244            // `#![crate_name = "..."]` as a workaround. This is okay since we're only checking if
245            // the fixed code is compilable.
246            if self.revision.is_some() {
247                let crate_name =
248                    self.testpaths.file.file_stem().expect("test must have a file stem");
249                // crate name must be alphanumeric or `_`.
250                // replace `a.foo` -> `a__foo` for crate name purposes.
251                // replace `revision-name-with-dashes` -> `revision_name_with_underscore`
252                let crate_name = crate_name.replace('.', "__");
253                let crate_name = crate_name.replace('-', "_");
254                rustc.arg("--crate-name");
255                rustc.arg(crate_name);
256            }
257
258            let res = self.compose_and_run_compiler(rustc, None);
259            if !res.status.success() {
260                self.fatal_proc_rec("failed to compile fixed code", &res);
261            }
262            if !res.stderr.is_empty()
263                && !self.props.rustfix_only_machine_applicable
264                && !json::rustfix_diagnostics_only(&res.stderr).is_empty()
265            {
266                self.fatal_proc_rec("fixed code is still producing diagnostics", &res);
267            }
268        }
269    }
270}