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