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