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::common::PassFailMode;
9use crate::json;
10use crate::runtest::{
11 AllowUnused, Emit, LinkToAux, ProcRes, RunResult, TargetLocation, TestCx, TestOutput,
12 TestSuite, Truncated, UI_FIXED, WillExecute,
13};
14
15impl TestCx<'_> {
16 pub(super) fn run_ui_test(&self) {
17 if self.config.suite == TestSuite::RustdocUi && self.props.should_fail {
18 writeln!(
19 self.stderr,
20 "`should-fail` should not be used in `rustdoc-ui` testsuite, use `failure-status` instead",
21 );
22 return;
25 }
26 let pass_fail =
27 self.effective_pass_fail_mode().expect("UI tests always have a pass/fail mode");
28
29 if pass_fail == PassFailMode::BuildFail {
30 let proc_res = self.compile_test(WillExecute::No, Emit::Metadata);
32 self.check_if_test_should_compile(PassFailMode::CheckPass, &proc_res);
33 }
34
35 let will_execute = if pass_fail.is_run() { self.run_if_enabled() } else { WillExecute::No };
36 let emit_metadata = if pass_fail.is_check() { Emit::Metadata } else { Emit::None };
37 let proc_res = self.compile_test(will_execute, emit_metadata);
38 self.check_if_test_should_compile(pass_fail, &proc_res);
39
40 if matches!(proc_res.truncated, Truncated::Yes)
41 && !self.props.dont_check_compiler_stdout
42 && !self.props.dont_check_compiler_stderr
43 {
44 self.fatal_proc_rec(
45 "compiler output got truncated, cannot compare with reference file",
46 &proc_res,
47 );
48 }
49
50 let explicit = self.props.compile_flags.iter().any(|s| s.contains("--error-format"));
54
55 let expected_fixed = self.load_expected_output(UI_FIXED);
56
57 self.check_and_prune_duplicate_outputs(&proc_res, &[], &[]);
58
59 let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit);
60 let rustfix_input = json::rustfix_diagnostics_only(&proc_res.stderr);
61
62 if self.config.compare_mode.is_some() {
63 } else if self.config.rustfix_coverage {
65 let suggestions = get_suggestions_from_json(
71 &rustfix_input,
72 &HashSet::new(),
73 Filter::MachineApplicableOnly,
74 )
75 .unwrap_or_default();
76 if !suggestions.is_empty()
77 && !self.props.run_rustfix
78 && !self.props.rustfix_only_machine_applicable
79 {
80 let mut coverage_file_path = self.config.build_test_suite_root.clone();
81 coverage_file_path.push("rustfix_missing_coverage.txt");
82 debug!("coverage_file_path: {}", coverage_file_path);
83
84 let mut file = OpenOptions::new()
85 .create(true)
86 .append(true)
87 .open(coverage_file_path.as_path())
88 .expect("could not create or open file");
89
90 if let Err(e) = writeln!(file, "{}", self.testpaths.file) {
91 panic!("couldn't write to {}: {e:?}", coverage_file_path);
92 }
93 }
94 } else if self.props.run_rustfix {
95 let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file).unwrap();
97 let suggestions = get_suggestions_from_json(
98 &rustfix_input,
99 &HashSet::new(),
100 if self.props.rustfix_only_machine_applicable {
101 Filter::MachineApplicableOnly
102 } else {
103 Filter::Everything
104 },
105 )
106 .unwrap();
107 let fixed_code = apply_suggestions(&unfixed_code, &suggestions).unwrap_or_else(|e| {
108 panic!(
109 "failed to apply suggestions for {:?} with rustfix: {}",
110 self.testpaths.file, e
111 )
112 });
113
114 if self
115 .compare_output("fixed", &fixed_code, &fixed_code, &expected_fixed)
116 .should_error()
117 {
118 errors += 1;
119 }
120 } else if !expected_fixed.is_empty() {
121 panic!(
122 "the `//@ run-rustfix` directive wasn't found but a `*.fixed` \
123 file was found"
124 );
125 }
126
127 if errors > 0 {
128 writeln!(
129 self.stdout,
130 "To update references, rerun the tests and pass the `--bless` flag"
131 );
132 let relative_path_to_file =
133 self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
134 writeln!(
135 self.stdout,
136 "To only update this specific test, also pass `--test-args {}`",
137 relative_path_to_file,
138 );
139 self.fatal_proc_rec(
140 &format!("{} errors occurred comparing output.", errors),
141 &proc_res,
142 );
143 }
144
145 let mut run_proc_res: Option<ProcRes> = None;
148 let output_to_check = if will_execute == WillExecute::Yes {
149 let proc_res = self.exec_compiled_test();
150 let run_output_errors = if self.props.check_run_results {
151 self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
152 } else {
153 0
154 };
155 if run_output_errors > 0 {
156 self.fatal_proc_rec(
157 &format!("{} errors occurred comparing run output.", run_output_errors),
158 &proc_res,
159 );
160 }
161 let code = proc_res.status.code();
162 let run_result = if proc_res.status.success() {
163 RunResult::Pass
164 } else if code.is_some_and(|c| c >= 1 && c <= 127) {
165 RunResult::Fail
166 } else {
167 RunResult::Crash
168 };
169
170 let pass_hint = format!("code={code:?} so test would pass with `{run_result}`");
173 match pass_fail {
174 PassFailMode::CheckFail
175 | PassFailMode::CheckPass
176 | PassFailMode::BuildFail
177 | PassFailMode::BuildPass => {
178 unreachable!("test program should not have run in mode {pass_fail:?}")
179 }
180
181 PassFailMode::RunPass => {
182 if run_result != RunResult::Pass {
183 self.fatal_proc_rec(
184 &format!("test did not exit with success! {pass_hint}"),
185 &proc_res,
186 );
187 }
188 }
189
190 PassFailMode::RunFail => {
191 let crash_ok = !self.config.can_unwind();
195 if run_result != RunResult::Fail
196 && !(crash_ok && run_result == RunResult::Crash)
197 {
198 let err = if crash_ok {
199 format!(
200 "test did not exit with failure or crash (`{}` can't unwind)! {pass_hint}",
201 self.config.target
202 )
203 } else {
204 format!("test did not exit with failure! {pass_hint}")
205 };
206 self.fatal_proc_rec(&err, &proc_res);
207 }
208 }
209
210 PassFailMode::RunCrash => {
211 if run_result != RunResult::Crash {
212 self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res);
213 }
214 }
215
216 PassFailMode::RunFailOrCrash => {
217 if run_result != RunResult::Fail && run_result != RunResult::Crash {
218 self.fatal_proc_rec(
219 &format!("test did not exit with failure or crash! {pass_hint}"),
220 &proc_res,
221 );
222 }
223 }
224 }
225
226 let output = self.get_output(&proc_res);
227 run_proc_res = Some(proc_res);
229 output
230 } else {
231 self.get_output(&proc_res)
232 };
233
234 debug!(
235 "run_ui_test: explicit={:?} config.compare_mode={:?} \
236 proc_res.status={:?} props.error_patterns={:?} output_to_check={:?}",
237 explicit,
238 self.config.compare_mode,
239 proc_res.status,
240 self.props.error_patterns,
241 output_to_check,
242 );
243
244 self.check_expected_errors(&proc_res);
246
247 let pattern_proc_res = run_proc_res.as_ref().unwrap_or(&proc_res);
250 self.check_all_error_patterns(&output_to_check, pattern_proc_res);
251 self.check_forbid_output(&output_to_check, pattern_proc_res);
252
253 if self.props.run_rustfix && self.config.compare_mode.is_none() {
254 let mut rustc = self.make_compile_args(
257 self.compiler_kind_for_non_aux(),
258 &self.expected_output_path(UI_FIXED),
259 TargetLocation::ThisFile(self.make_exe_name()),
260 emit_metadata,
261 AllowUnused::No,
262 LinkToAux::Yes,
263 Vec::new(),
264 );
265
266 if self.variant.revision.is_some() {
272 let crate_name =
273 self.testpaths.file.file_stem().expect("test must have a file stem");
274 let crate_name = crate_name.replace('.', "__");
278 let crate_name = crate_name.replace('-', "_");
279 rustc.arg("--crate-name");
280 rustc.arg(crate_name);
281 }
282
283 let res = self.compose_and_run_compiler(rustc, None);
284 if !res.status.success() {
285 self.fatal_proc_rec("failed to compile fixed code", &res);
286 }
287 if !res.stderr.is_empty()
288 && !self.props.rustfix_only_machine_applicable
289 && !json::rustfix_diagnostics_only(&res.stderr).is_empty()
290 {
291 self.fatal_proc_rec("fixed code is still producing diagnostics", &res);
292 }
293 }
294 }
295}