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, ErrorKind, FailMode, LinkToAux, PassMode, TargetLocation, TestCx,
10    TestOutput, Truncated, UI_FIXED, WillExecute,
11};
12use crate::{errors, json};
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 pm = Some(PassMode::Check);
19            let proc_res =
20                self.compile_test_general(WillExecute::No, Emit::Metadata, pm, Vec::new());
21            self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res);
22        }
23
24        let pm = self.pass_mode();
25        let should_run = self.should_run(pm);
26        let emit_metadata = self.should_emit_metadata(pm);
27        let proc_res = self.compile_test(should_run, emit_metadata);
28        self.check_if_test_should_compile(self.props.fail_mode, pm, &proc_res);
29        if matches!(proc_res.truncated, Truncated::Yes)
30            && !self.props.dont_check_compiler_stdout
31            && !self.props.dont_check_compiler_stderr
32        {
33            self.fatal_proc_rec(
34                "compiler output got truncated, cannot compare with reference file",
35                &proc_res,
36            );
37        }
38
39        // if the user specified a format in the ui test
40        // print the output to the stderr file, otherwise extract
41        // the rendered error messages from json and print them
42        let explicit = self.props.compile_flags.iter().any(|s| s.contains("--error-format"));
43
44        let expected_fixed = self.load_expected_output(UI_FIXED);
45
46        self.check_and_prune_duplicate_outputs(&proc_res, &[], &[]);
47
48        let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit);
49        let rustfix_input = json::rustfix_diagnostics_only(&proc_res.stderr);
50
51        if self.config.compare_mode.is_some() {
52            // don't test rustfix with nll right now
53        } else if self.config.rustfix_coverage {
54            // Find out which tests have `MachineApplicable` suggestions but are missing
55            // `run-rustfix` or `run-rustfix-only-machine-applicable` headers.
56            //
57            // This will return an empty `Vec` in case the executed test file has a
58            // `compile-flags: --error-format=xxxx` header with a value other than `json`.
59            let suggestions = get_suggestions_from_json(
60                &rustfix_input,
61                &HashSet::new(),
62                Filter::MachineApplicableOnly,
63            )
64            .unwrap_or_default();
65            if !suggestions.is_empty()
66                && !self.props.run_rustfix
67                && !self.props.rustfix_only_machine_applicable
68            {
69                let mut coverage_file_path = self.config.build_test_suite_root.clone();
70                coverage_file_path.push("rustfix_missing_coverage.txt");
71                debug!("coverage_file_path: {}", coverage_file_path.display());
72
73                let mut file = OpenOptions::new()
74                    .create(true)
75                    .append(true)
76                    .open(coverage_file_path.as_path())
77                    .expect("could not create or open file");
78
79                if let Err(e) = writeln!(file, "{}", self.testpaths.file.display()) {
80                    panic!("couldn't write to {}: {e:?}", coverage_file_path.display());
81                }
82            }
83        } else if self.props.run_rustfix {
84            // Apply suggestions from rustc to the code itself
85            let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file).unwrap();
86            let suggestions = get_suggestions_from_json(
87                &rustfix_input,
88                &HashSet::new(),
89                if self.props.rustfix_only_machine_applicable {
90                    Filter::MachineApplicableOnly
91                } else {
92                    Filter::Everything
93                },
94            )
95            .unwrap();
96            let fixed_code = apply_suggestions(&unfixed_code, &suggestions).unwrap_or_else(|e| {
97                panic!(
98                    "failed to apply suggestions for {:?} with rustfix: {}",
99                    self.testpaths.file, e
100                )
101            });
102
103            if self
104                .compare_output("fixed", &fixed_code, &fixed_code, &expected_fixed)
105                .should_error()
106            {
107                errors += 1;
108            }
109        } else if !expected_fixed.is_empty() {
110            panic!(
111                "the `//@ run-rustfix` directive wasn't found but a `*.fixed` \
112                 file was found"
113            );
114        }
115
116        if errors > 0 {
117            println!("To update references, rerun the tests and pass the `--bless` flag");
118            let relative_path_to_file =
119                self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
120            println!(
121                "To only update this specific test, also pass `--test-args {}`",
122                relative_path_to_file.display(),
123            );
124            self.fatal_proc_rec(
125                &format!("{} errors occurred comparing output.", errors),
126                &proc_res,
127            );
128        }
129
130        let expected_errors = errors::load_errors(&self.testpaths.file, self.revision);
131
132        if let WillExecute::Yes = should_run {
133            let proc_res = self.exec_compiled_test();
134            let run_output_errors = if self.props.check_run_results {
135                self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
136            } else {
137                0
138            };
139            if run_output_errors > 0 {
140                self.fatal_proc_rec(
141                    &format!("{} errors occurred comparing run output.", run_output_errors),
142                    &proc_res,
143                );
144            }
145            if self.should_run_successfully(pm) {
146                if !proc_res.status.success() {
147                    self.fatal_proc_rec("test run failed!", &proc_res);
148                }
149            } else if proc_res.status.success() {
150                self.fatal_proc_rec("test run succeeded!", &proc_res);
151            }
152
153            let output_to_check = self.get_output(&proc_res);
154            if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty()
155            {
156                // "// error-pattern" comments
157                self.check_all_error_patterns(&output_to_check, &proc_res, pm);
158            }
159            self.check_forbid_output(&output_to_check, &proc_res)
160        }
161
162        debug!(
163            "run_ui_test: explicit={:?} config.compare_mode={:?} expected_errors={:?} \
164               proc_res.status={:?} props.error_patterns={:?}",
165            explicit,
166            self.config.compare_mode,
167            expected_errors,
168            proc_res.status,
169            self.props.error_patterns
170        );
171
172        let check_patterns = should_run == WillExecute::No
173            && (!self.props.error_patterns.is_empty()
174                || !self.props.regex_error_patterns.is_empty());
175        if !explicit && self.config.compare_mode.is_none() {
176            let check_annotations = !check_patterns || !expected_errors.is_empty();
177
178            if check_annotations {
179                // "//~ERROR comments"
180                self.check_expected_errors(expected_errors, &proc_res);
181            }
182        } else if explicit && !expected_errors.is_empty() {
183            let msg = format!(
184                "line {}: cannot combine `--error-format` with {} annotations; use `error-pattern` instead",
185                expected_errors[0].line_num,
186                expected_errors[0].kind.unwrap_or(ErrorKind::Error),
187            );
188            self.fatal(&msg);
189        }
190        let output_to_check = self.get_output(&proc_res);
191        if check_patterns {
192            // "// error-pattern" comments
193            self.check_all_error_patterns(&output_to_check, &proc_res, pm);
194        }
195        self.check_forbid_output(&output_to_check, &proc_res);
196
197        if self.props.run_rustfix && self.config.compare_mode.is_none() {
198            // And finally, compile the fixed code and make sure it both
199            // succeeds and has no diagnostics.
200            let mut rustc = self.make_compile_args(
201                &self.expected_output_path(UI_FIXED),
202                TargetLocation::ThisFile(self.make_exe_name()),
203                emit_metadata,
204                AllowUnused::No,
205                LinkToAux::Yes,
206                Vec::new(),
207            );
208
209            // If a test is revisioned, it's fixed source file can be named "a.foo.fixed", which,
210            // well, "a.foo" isn't a valid crate name. So we explicitly mangle the test name
211            // (including the revision) here to avoid the test writer having to manually specify a
212            // `#![crate_name = "..."]` as a workaround. This is okay since we're only checking if
213            // the fixed code is compilable.
214            if self.revision.is_some() {
215                let crate_name =
216                    self.testpaths.file.file_stem().expect("test must have a file stem");
217                // crate name must be alphanumeric or `_`.
218                let crate_name =
219                    crate_name.to_str().expect("crate name implies file name must be valid UTF-8");
220                // replace `a.foo` -> `a__foo` for crate name purposes.
221                // replace `revision-name-with-dashes` -> `revision_name_with_underscore`
222                let crate_name = crate_name.replace('.', "__");
223                let crate_name = crate_name.replace('-', "_");
224                rustc.arg("--crate-name");
225                rustc.arg(crate_name);
226            }
227
228            let res = self.compose_and_run_compiler(rustc, None, self.testpaths);
229            if !res.status.success() {
230                self.fatal_proc_rec("failed to compile fixed code", &res);
231            }
232            if !res.stderr.is_empty()
233                && !self.props.rustfix_only_machine_applicable
234                && !json::rustfix_diagnostics_only(&res.stderr).is_empty()
235            {
236                self.fatal_proc_rec("fixed code is still producing diagnostics", &res);
237            }
238        }
239    }
240}