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 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 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 } else if self.config.rustfix_coverage {
54 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);
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) {
80 panic!("couldn't write to {}: {e:?}", coverage_file_path);
81 }
82 }
83 } else if self.props.run_rustfix {
84 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 writeln!(
118 self.stdout,
119 "To update references, rerun the tests and pass the `--bless` flag"
120 );
121 let relative_path_to_file =
122 self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
123 writeln!(
124 self.stdout,
125 "To only update this specific test, also pass `--test-args {}`",
126 relative_path_to_file,
127 );
128 self.fatal_proc_rec(
129 &format!("{} errors occurred comparing output.", errors),
130 &proc_res,
131 );
132 }
133
134 let mut run_proc_res: Option<ProcRes> = None;
137 let output_to_check = if let WillExecute::Yes = should_run {
138 let proc_res = self.exec_compiled_test();
139 let run_output_errors = if self.props.check_run_results {
140 self.load_compare_outputs(&proc_res, TestOutput::Run, explicit)
141 } else {
142 0
143 };
144 if run_output_errors > 0 {
145 self.fatal_proc_rec(
146 &format!("{} errors occurred comparing run output.", run_output_errors),
147 &proc_res,
148 );
149 }
150 let code = proc_res.status.code();
151 let run_result = if proc_res.status.success() {
152 RunResult::Pass
153 } else if code.is_some_and(|c| c >= 1 && c <= 127) {
154 RunResult::Fail
155 } else {
156 RunResult::Crash
157 };
158 let pass_hint = format!("code={code:?} so test would pass with `{run_result}`");
161 if self.should_run_successfully(pm) {
162 if run_result != RunResult::Pass {
163 self.fatal_proc_rec(
164 &format!("test did not exit with success! {pass_hint}"),
165 &proc_res,
166 );
167 }
168 } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Fail)) {
169 let crash_ok = !self.config.can_unwind();
173 if run_result != RunResult::Fail && !(crash_ok && run_result == RunResult::Crash) {
174 let err = if crash_ok {
175 format!(
176 "test did not exit with failure or crash (`{}` can't unwind)! {pass_hint}",
177 self.config.target
178 )
179 } else {
180 format!("test did not exit with failure! {pass_hint}")
181 };
182 self.fatal_proc_rec(&err, &proc_res);
183 }
184 } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::Crash)) {
185 if run_result != RunResult::Crash {
186 self.fatal_proc_rec(&format!("test did not crash! {pass_hint}"), &proc_res);
187 }
188 } else if self.props.fail_mode == Some(FailMode::Run(RunFailMode::FailOrCrash)) {
189 if run_result != RunResult::Fail && run_result != RunResult::Crash {
190 self.fatal_proc_rec(
191 &format!("test did not exit with failure or crash! {pass_hint}"),
192 &proc_res,
193 );
194 }
195 } else {
196 unreachable!("run_ui_test() must not be called if the test should not run");
197 }
198
199 let output = self.get_output(&proc_res);
200 run_proc_res = Some(proc_res);
202 output
203 } else {
204 self.get_output(&proc_res)
205 };
206
207 debug!(
208 "run_ui_test: explicit={:?} config.compare_mode={:?} \
209 proc_res.status={:?} props.error_patterns={:?} output_to_check={:?}",
210 explicit,
211 self.config.compare_mode,
212 proc_res.status,
213 self.props.error_patterns,
214 output_to_check,
215 );
216
217 self.check_expected_errors(&proc_res);
219
220 let pattern_proc_res = run_proc_res.as_ref().unwrap_or(&proc_res);
223 self.check_all_error_patterns(&output_to_check, pattern_proc_res);
224 self.check_forbid_output(&output_to_check, pattern_proc_res);
225
226 if self.props.run_rustfix && self.config.compare_mode.is_none() {
227 let mut rustc = self.make_compile_args(
230 self.compiler_kind_for_non_aux(),
231 &self.expected_output_path(UI_FIXED),
232 TargetLocation::ThisFile(self.make_exe_name()),
233 emit_metadata,
234 AllowUnused::No,
235 LinkToAux::Yes,
236 Vec::new(),
237 );
238
239 if self.revision.is_some() {
245 let crate_name =
246 self.testpaths.file.file_stem().expect("test must have a file stem");
247 let crate_name = crate_name.replace('.', "__");
251 let crate_name = crate_name.replace('-', "_");
252 rustc.arg("--crate-name");
253 rustc.arg(crate_name);
254 }
255
256 let res = self.compose_and_run_compiler(rustc, None);
257 if !res.status.success() {
258 self.fatal_proc_rec("failed to compile fixed code", &res);
259 }
260 if !res.stderr.is_empty()
261 && !self.props.rustfix_only_machine_applicable
262 && !json::rustfix_diagnostics_only(&res.stderr).is_empty()
263 {
264 self.fatal_proc_rec("fixed code is still producing diagnostics", &res);
265 }
266 }
267 }
268}