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