]>
Commit | Line | Data |
---|---|---|
48663c56 XL |
1 | // ignore-tidy-filelength |
2 | ||
9fa01778 | 3 | use crate::common::{expected_output_path, UI_EXTENSIONS, UI_FIXED, UI_STDERR, UI_STDOUT}; |
c295e0f8 | 4 | use crate::common::{incremental_dir, output_base_dir, output_base_name, output_testname_unique}; |
fc512014 | 5 | use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJson, Ui}; |
dfeec247 XL |
6 | use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc}; |
7 | use crate::common::{CompareMode, FailMode, PassMode}; | |
9fa01778 | 8 | use crate::common::{Config, TestPaths}; |
5869c6ff | 9 | use crate::common::{Pretty, RunPassValgrind}; |
dfeec247 | 10 | use crate::common::{UI_RUN_STDERR, UI_RUN_STDOUT}; |
c295e0f8 | 11 | use crate::compute_diff::{write_diff, write_filtered_diff}; |
9fa01778 | 12 | use crate::errors::{self, Error, ErrorKind}; |
9fa01778 XL |
13 | use crate::header::TestProps; |
14 | use crate::json; | |
c295e0f8 | 15 | use crate::read2::read2_abbreviated; |
487cf647 | 16 | use crate::util::{add_dylib_path, dylib_env_var, logv, PathBufExt}; |
6a06907d | 17 | use crate::ColorConfig; |
532ac7d7 | 18 | use regex::{Captures, Regex}; |
8faf50e0 | 19 | use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; |
a7813a04 | 20 | |
94b46f34 | 21 | use std::collections::hash_map::DefaultHasher; |
c295e0f8 | 22 | use std::collections::{HashMap, HashSet}; |
32a655c1 | 23 | use std::env; |
0731742a | 24 | use std::ffi::{OsStr, OsString}; |
532ac7d7 | 25 | use std::fs::{self, create_dir_all, File, OpenOptions}; |
94b46f34 | 26 | use std::hash::{Hash, Hasher}; |
a7813a04 | 27 | use std::io::prelude::*; |
32a655c1 | 28 | use std::io::{self, BufReader}; |
487cf647 | 29 | use std::iter; |
a7813a04 | 30 | use std::path::{Path, PathBuf}; |
923072b8 | 31 | use std::process::{Child, Command, ExitStatus, Output, Stdio}; |
a7813a04 XL |
32 | use std::str; |
33 | ||
3dfed10e | 34 | use glob::glob; |
48663c56 | 35 | use lazy_static::lazy_static; |
3dfed10e | 36 | use tracing::*; |
48663c56 | 37 | |
9fa01778 XL |
38 | use crate::extract_gdb_version; |
39 | use crate::is_android_gdb_target; | |
c30ab7b3 | 40 | |
c295e0f8 XL |
41 | mod debugger; |
42 | use debugger::{check_debugger_output, DebuggerCommands}; | |
43 | ||
416331ca XL |
44 | #[cfg(test)] |
45 | mod tests; | |
46 | ||
9c376795 FG |
47 | const FAKE_SRC_BASE: &str = "fake-test-src-base"; |
48 | ||
83c7162d XL |
49 | #[cfg(windows)] |
50 | fn disable_error_reporting<F: FnOnce() -> R, R>(f: F) -> R { | |
51 | use std::sync::Mutex; | |
dfeec247 XL |
52 | use winapi::um::errhandlingapi::SetErrorMode; |
53 | use winapi::um::winbase::SEM_NOGPFAULTERRORBOX; | |
83c7162d XL |
54 | |
55 | lazy_static! { | |
f9f354fc | 56 | static ref LOCK: Mutex<()> = Mutex::new(()); |
83c7162d XL |
57 | } |
58 | // Error mode is a global variable, so lock it so only one thread will change it | |
59 | let _lock = LOCK.lock().unwrap(); | |
60 | ||
61 | // Tell Windows to not show any UI on errors (such as terminating abnormally). | |
62 | // This is important for running tests, since some of them use abnormal | |
63 | // termination by design. This mode is inherited by all child processes. | |
64 | unsafe { | |
65 | let old_mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); // read inherited flags | |
66 | SetErrorMode(old_mode | SEM_NOGPFAULTERRORBOX); | |
67 | let r = f(); | |
68 | SetErrorMode(old_mode); | |
69 | r | |
70 | } | |
71 | } | |
72 | ||
73 | #[cfg(not(windows))] | |
74 | fn disable_error_reporting<F: FnOnce() -> R, R>(f: F) -> R { | |
75 | f() | |
76 | } | |
77 | ||
48663c56 XL |
78 | /// The platform-specific library name |
79 | pub fn get_lib_name(lib: &str, dylib: bool) -> String { | |
80 | // In some casess (e.g. MUSL), we build a static | |
81 | // library, rather than a dynamic library. | |
82 | // In this case, the only path we can pass | |
83 | // with '--extern-meta' is the '.lib' file | |
84 | if !dylib { | |
85 | return format!("lib{}.rlib", lib); | |
86 | } | |
87 | ||
88 | if cfg!(windows) { | |
89 | format!("{}.dll", lib) | |
90 | } else if cfg!(target_os = "macos") { | |
91 | format!("lib{}.dylib", lib) | |
92 | } else { | |
93 | format!("lib{}.so", lib) | |
94 | } | |
95 | } | |
96 | ||
94b46f34 | 97 | pub fn run(config: Config, testpaths: &TestPaths, revision: Option<&str>) { |
a7813a04 | 98 | match &*config.target { |
0731742a XL |
99 | "arm-linux-androideabi" |
100 | | "armv7-linux-androideabi" | |
101 | | "thumbv7neon-linux-androideabi" | |
102 | | "aarch64-linux-android" => { | |
a7813a04 XL |
103 | if !config.adb_device_status { |
104 | panic!("android device not available"); | |
105 | } | |
106 | } | |
107 | ||
c30ab7b3 | 108 | _ => { |
8bb4bdeb | 109 | // android has its own gdb handling |
dfeec247 | 110 | if config.debugger == Some(Debugger::Gdb) && config.gdb.is_none() { |
c30ab7b3 SL |
111 | panic!("gdb not available but debuginfo gdb debuginfo test requested"); |
112 | } | |
113 | } | |
a7813a04 XL |
114 | } |
115 | ||
116 | if config.verbose { | |
117 | // We're going to be dumping a lot of info. Start on a new line. | |
118 | print!("\n\n"); | |
119 | } | |
120 | debug!("running {:?}", testpaths.file.display()); | |
c295e0f8 | 121 | let mut props = TestProps::from_file(&testpaths.file, revision, &config); |
9c376795 FG |
122 | |
123 | // For non-incremental (i.e. regular UI) tests, the incremental directory | |
124 | // takes into account the revision name, since the revisions are independent | |
125 | // of each other and can race. | |
c295e0f8 | 126 | if props.incremental { |
9c376795 | 127 | props.incremental_dir = Some(incremental_dir(&config, testpaths, revision)); |
c295e0f8 | 128 | } |
a7813a04 | 129 | |
3dfed10e | 130 | let cx = TestCx { config: &config, props: &props, testpaths, revision }; |
94b46f34 | 131 | create_dir_all(&cx.output_base_dir()).unwrap(); |
c295e0f8 XL |
132 | if props.incremental { |
133 | cx.init_incremental_test(); | |
134 | } | |
a7813a04 | 135 | |
94b46f34 XL |
136 | if config.mode == Incremental { |
137 | // Incremental tests are special because they cannot be run in | |
138 | // parallel. | |
dfeec247 | 139 | assert!(!props.revisions.is_empty(), "Incremental tests require revisions."); |
94b46f34 | 140 | for revision in &props.revisions { |
c295e0f8 XL |
141 | let mut revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config); |
142 | revision_props.incremental_dir = props.incremental_dir.clone(); | |
a7813a04 XL |
143 | let rev_cx = TestCx { |
144 | config: &config, | |
145 | props: &revision_props, | |
3b2f2976 | 146 | testpaths, |
ff7c6d11 | 147 | revision: Some(revision), |
a7813a04 XL |
148 | }; |
149 | rev_cx.run_revision(); | |
150 | } | |
94b46f34 XL |
151 | } else { |
152 | cx.run_revision(); | |
a7813a04 XL |
153 | } |
154 | ||
94b46f34 XL |
155 | cx.create_stamp(); |
156 | } | |
8bb4bdeb | 157 | |
94b46f34 XL |
158 | pub fn compute_stamp_hash(config: &Config) -> String { |
159 | let mut hash = DefaultHasher::new(); | |
160 | config.stage_id.hash(&mut hash); | |
17df50a5 | 161 | config.run.hash(&mut hash); |
0bf4aa26 | 162 | |
dfeec247 XL |
163 | match config.debugger { |
164 | Some(Debugger::Cdb) => { | |
165 | config.cdb.hash(&mut hash); | |
166 | } | |
dc9dc135 | 167 | |
dfeec247 XL |
168 | Some(Debugger::Gdb) => { |
169 | config.gdb.hash(&mut hash); | |
170 | env::var_os("PATH").hash(&mut hash); | |
171 | env::var_os("PYTHONPATH").hash(&mut hash); | |
172 | } | |
0bf4aa26 | 173 | |
dfeec247 | 174 | Some(Debugger::Lldb) => { |
04454e1e | 175 | config.python.hash(&mut hash); |
dfeec247 XL |
176 | config.lldb_python_dir.hash(&mut hash); |
177 | env::var_os("PATH").hash(&mut hash); | |
178 | env::var_os("PYTHONPATH").hash(&mut hash); | |
179 | } | |
180 | ||
181 | None => {} | |
0bf4aa26 XL |
182 | } |
183 | ||
dfeec247 | 184 | if let Ui = config.mode { |
dc9dc135 XL |
185 | config.force_pass_mode.hash(&mut hash); |
186 | } | |
187 | ||
94b46f34 | 188 | format!("{:x}", hash.finish()) |
a7813a04 XL |
189 | } |
190 | ||
fc512014 | 191 | #[derive(Copy, Clone)] |
a7813a04 XL |
192 | struct TestCx<'test> { |
193 | config: &'test Config, | |
194 | props: &'test TestProps, | |
195 | testpaths: &'test TestPaths, | |
ff7c6d11 | 196 | revision: Option<&'test str>, |
a7813a04 XL |
197 | } |
198 | ||
0bf4aa26 XL |
199 | enum ReadFrom { |
200 | Path, | |
201 | Stdin(String), | |
202 | } | |
203 | ||
e1599b0c XL |
204 | enum TestOutput { |
205 | Compile, | |
206 | Run, | |
207 | } | |
208 | ||
dfeec247 XL |
209 | /// Will this test be executed? Should we use `make_exe_name`? |
210 | #[derive(Copy, Clone, PartialEq)] | |
211 | enum WillExecute { | |
212 | Yes, | |
213 | No, | |
17df50a5 | 214 | Disabled, |
dfeec247 XL |
215 | } |
216 | ||
487cf647 | 217 | /// What value should be passed to `--emit`? |
dfeec247 | 218 | #[derive(Copy, Clone)] |
487cf647 FG |
219 | enum Emit { |
220 | None, | |
221 | Metadata, | |
222 | LlvmIr, | |
223 | Asm, | |
dfeec247 XL |
224 | } |
225 | ||
a7813a04 | 226 | impl<'test> TestCx<'test> { |
a7813a04 XL |
227 | /// Code executed for each revision in turn (or, if there are no |
228 | /// revisions, exactly once, with revision == None). | |
229 | fn run_revision(&self) { | |
c295e0f8 XL |
230 | if self.props.should_ice && self.config.mode != Incremental { |
231 | self.fatal("cannot use should-ice in a test that is not cfail"); | |
60c5eb7d | 232 | } |
a7813a04 | 233 | match self.config.mode { |
a7813a04 XL |
234 | RunPassValgrind => self.run_valgrind_test(), |
235 | Pretty => self.run_pretty_test(), | |
dfeec247 | 236 | DebugInfo => self.run_debuginfo_test(), |
a7813a04 XL |
237 | Codegen => self.run_codegen_test(), |
238 | Rustdoc => self.run_rustdoc_test(), | |
fc512014 | 239 | RustdocJson => self.run_rustdoc_json_test(), |
a7813a04 XL |
240 | CodegenUnits => self.run_codegen_units_test(), |
241 | Incremental => self.run_incremental_test(), | |
242 | RunMake => self.run_rmake_test(), | |
416331ca | 243 | Ui => self.run_ui_test(), |
5bcae85e | 244 | MirOpt => self.run_mir_opt_test(), |
532ac7d7 | 245 | Assembly => self.run_assembly_test(), |
9fa01778 | 246 | JsDocTest => self.run_js_doc_test(), |
a7813a04 XL |
247 | } |
248 | } | |
249 | ||
dc9dc135 XL |
250 | fn pass_mode(&self) -> Option<PassMode> { |
251 | self.props.pass_mode(self.config) | |
252 | } | |
253 | ||
dfeec247 | 254 | fn should_run(&self, pm: Option<PassMode>) -> WillExecute { |
17df50a5 XL |
255 | let test_should_run = match self.config.mode { |
256 | Ui if pm == Some(PassMode::Run) || self.props.fail_mode == Some(FailMode::Run) => true, | |
257 | MirOpt if pm == Some(PassMode::Run) => true, | |
258 | Ui | MirOpt => false, | |
e74abb32 | 259 | mode => panic!("unimplemented for mode {:?}", mode), |
17df50a5 XL |
260 | }; |
261 | if test_should_run { self.run_if_enabled() } else { WillExecute::No } | |
262 | } | |
263 | ||
264 | fn run_if_enabled(&self) -> WillExecute { | |
265 | if self.config.run_enabled() { WillExecute::Yes } else { WillExecute::Disabled } | |
e74abb32 XL |
266 | } |
267 | ||
dfeec247 | 268 | fn should_run_successfully(&self, pm: Option<PassMode>) -> bool { |
dc9dc135 | 269 | match self.config.mode { |
ba9703b0 | 270 | Ui | MirOpt => pm == Some(PassMode::Run), |
dc9dc135 XL |
271 | mode => panic!("unimplemented for mode {:?}", mode), |
272 | } | |
0bf4aa26 XL |
273 | } |
274 | ||
dfeec247 | 275 | fn should_compile_successfully(&self, pm: Option<PassMode>) -> bool { |
0bf4aa26 | 276 | match self.config.mode { |
9fa01778 | 277 | JsDocTest => true, |
dfeec247 | 278 | Ui => pm.is_some() || self.props.fail_mode > Some(FailMode::Build), |
0bf4aa26 | 279 | Incremental => { |
dfeec247 XL |
280 | let revision = |
281 | self.revision.expect("incremental tests require a list of revisions"); | |
0bf4aa26 XL |
282 | if revision.starts_with("rpass") || revision.starts_with("rfail") { |
283 | true | |
284 | } else if revision.starts_with("cfail") { | |
285 | // FIXME: would be nice if incremental revs could start with "cpass" | |
dfeec247 | 286 | pm.is_some() |
0bf4aa26 XL |
287 | } else { |
288 | panic!("revision name must begin with rpass, rfail, or cfail"); | |
289 | } | |
290 | } | |
291 | mode => panic!("unimplemented for mode {:?}", mode), | |
292 | } | |
293 | } | |
294 | ||
dfeec247 XL |
295 | fn check_if_test_should_compile(&self, proc_res: &ProcRes, pm: Option<PassMode>) { |
296 | if self.should_compile_successfully(pm) { | |
9e0c209e | 297 | if !proc_res.status.success() { |
ff7c6d11 | 298 | self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res); |
9e0c209e SL |
299 | } |
300 | } else { | |
301 | if proc_res.status.success() { | |
302 | self.fatal_proc_rec( | |
303 | &format!("{} test compiled successfully!", self.config.mode)[..], | |
ff7c6d11 XL |
304 | proc_res, |
305 | ); | |
9e0c209e | 306 | } |
a7813a04 | 307 | |
ff7c6d11 | 308 | self.check_correct_failure_status(proc_res); |
9e0c209e | 309 | } |
ff7c6d11 XL |
310 | } |
311 | ||
312 | fn run_cfail_test(&self) { | |
dfeec247 XL |
313 | let pm = self.pass_mode(); |
314 | let proc_res = self.compile_test(WillExecute::No, self.should_emit_metadata(pm)); | |
315 | self.check_if_test_should_compile(&proc_res, pm); | |
60c5eb7d | 316 | self.check_no_compiler_crash(&proc_res, self.props.should_ice); |
a7813a04 | 317 | |
a7813a04 XL |
318 | let output_to_check = self.get_output(&proc_res); |
319 | let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); | |
320 | if !expected_errors.is_empty() { | |
064997fb FG |
321 | if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty() |
322 | { | |
a7813a04 XL |
323 | self.fatal("both error pattern and expected errors specified"); |
324 | } | |
325 | self.check_expected_errors(expected_errors, &proc_res); | |
326 | } else { | |
064997fb | 327 | self.check_all_error_patterns(&output_to_check, &proc_res, pm); |
a7813a04 | 328 | } |
60c5eb7d XL |
329 | if self.props.should_ice { |
330 | match proc_res.status.code() { | |
331 | Some(101) => (), | |
332 | _ => self.fatal("expected ICE"), | |
333 | } | |
334 | } | |
9e0c209e | 335 | |
a7813a04 XL |
336 | self.check_forbid_output(&output_to_check, &proc_res); |
337 | } | |
338 | ||
339 | fn run_rfail_test(&self) { | |
dfeec247 | 340 | let pm = self.pass_mode(); |
17df50a5 XL |
341 | let should_run = self.run_if_enabled(); |
342 | let proc_res = self.compile_test(should_run, self.should_emit_metadata(pm)); | |
a7813a04 XL |
343 | |
344 | if !proc_res.status.success() { | |
345 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
346 | } | |
347 | ||
17df50a5 XL |
348 | if let WillExecute::Disabled = should_run { |
349 | return; | |
350 | } | |
351 | ||
a7813a04 XL |
352 | let proc_res = self.exec_compiled_test(); |
353 | ||
354 | // The value our Makefile configures valgrind to return on failure | |
355 | const VALGRIND_ERR: i32 = 100; | |
356 | if proc_res.status.code() == Some(VALGRIND_ERR) { | |
357 | self.fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res); | |
358 | } | |
359 | ||
360 | let output_to_check = self.get_output(&proc_res); | |
361 | self.check_correct_failure_status(&proc_res); | |
064997fb | 362 | self.check_all_error_patterns(&output_to_check, &proc_res, pm); |
a7813a04 XL |
363 | } |
364 | ||
365 | fn get_output(&self, proc_res: &ProcRes) -> String { | |
366 | if self.props.check_stdout { | |
367 | format!("{}{}", proc_res.stdout, proc_res.stderr) | |
368 | } else { | |
369 | proc_res.stderr.clone() | |
370 | } | |
371 | } | |
372 | ||
373 | fn check_correct_failure_status(&self, proc_res: &ProcRes) { | |
0531ce1d XL |
374 | let expected_status = Some(self.props.failure_status); |
375 | let received_status = proc_res.status.code(); | |
376 | ||
377 | if expected_status != received_status { | |
a7813a04 | 378 | self.fatal_proc_rec( |
94b46f34 XL |
379 | &format!( |
380 | "Error: expected failure status ({:?}) but received status {:?}.", | |
381 | expected_status, received_status | |
382 | ), | |
ff7c6d11 XL |
383 | proc_res, |
384 | ); | |
a7813a04 XL |
385 | } |
386 | } | |
387 | ||
388 | fn run_rpass_test(&self) { | |
dfeec247 | 389 | let emit_metadata = self.should_emit_metadata(self.pass_mode()); |
17df50a5 XL |
390 | let should_run = self.run_if_enabled(); |
391 | let proc_res = self.compile_test(should_run, emit_metadata); | |
a7813a04 XL |
392 | |
393 | if !proc_res.status.success() { | |
394 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
395 | } | |
396 | ||
17df50a5 XL |
397 | if let WillExecute::Disabled = should_run { |
398 | return; | |
399 | } | |
400 | ||
7cac9316 | 401 | // FIXME(#41968): Move this check to tidy? |
9e0c209e | 402 | let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); |
ff7c6d11 XL |
403 | assert!( |
404 | expected_errors.is_empty(), | |
405 | "run-pass tests with expected warnings should be moved to ui/" | |
406 | ); | |
9e0c209e | 407 | |
dc9dc135 XL |
408 | let proc_res = self.exec_compiled_test(); |
409 | if !proc_res.status.success() { | |
410 | self.fatal_proc_rec("test run failed!", &proc_res); | |
a7813a04 XL |
411 | } |
412 | } | |
413 | ||
414 | fn run_valgrind_test(&self) { | |
415 | assert!(self.revision.is_none(), "revisions not relevant here"); | |
416 | ||
417 | if self.config.valgrind_path.is_none() { | |
418 | assert!(!self.config.force_valgrind); | |
419 | return self.run_rpass_test(); | |
420 | } | |
421 | ||
17df50a5 | 422 | let should_run = self.run_if_enabled(); |
487cf647 | 423 | let mut proc_res = self.compile_test(should_run, Emit::None); |
a7813a04 XL |
424 | |
425 | if !proc_res.status.success() { | |
426 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
427 | } | |
428 | ||
17df50a5 XL |
429 | if let WillExecute::Disabled = should_run { |
430 | return; | |
431 | } | |
432 | ||
a7813a04 XL |
433 | let mut new_config = self.config.clone(); |
434 | new_config.runtool = new_config.valgrind_path.clone(); | |
dfeec247 | 435 | let new_cx = TestCx { config: &new_config, ..*self }; |
a7813a04 XL |
436 | proc_res = new_cx.exec_compiled_test(); |
437 | ||
438 | if !proc_res.status.success() { | |
439 | self.fatal_proc_rec("test run failed!", &proc_res); | |
440 | } | |
441 | } | |
442 | ||
443 | fn run_pretty_test(&self) { | |
444 | if self.props.pp_exact.is_some() { | |
445 | logv(self.config, "testing for exact pretty-printing".to_owned()); | |
446 | } else { | |
dfeec247 | 447 | logv(self.config, "testing for converging pretty-printing".to_owned()); |
a7813a04 XL |
448 | } |
449 | ||
ff7c6d11 XL |
450 | let rounds = match self.props.pp_exact { |
451 | Some(_) => 1, | |
452 | None => 2, | |
453 | }; | |
a7813a04 | 454 | |
0731742a | 455 | let src = fs::read_to_string(&self.testpaths.file).unwrap(); |
c30ab7b3 | 456 | let mut srcs = vec![src]; |
a7813a04 XL |
457 | |
458 | let mut round = 0; | |
459 | while round < rounds { | |
ff7c6d11 XL |
460 | logv( |
461 | self.config, | |
dfeec247 | 462 | format!("pretty-printing round {} revision {:?}", round, self.revision), |
ff7c6d11 | 463 | ); |
dfeec247 XL |
464 | let read_from = |
465 | if round == 0 { ReadFrom::Path } else { ReadFrom::Stdin(srcs[round].to_owned()) }; | |
a7813a04 | 466 | |
dfeec247 | 467 | let proc_res = self.print_source(read_from, &self.props.pretty_mode); |
a7813a04 | 468 | if !proc_res.status.success() { |
ff7c6d11 XL |
469 | self.fatal_proc_rec( |
470 | &format!( | |
471 | "pretty-printing failed in round {} revision {:?}", | |
94b46f34 | 472 | round, self.revision |
ff7c6d11 XL |
473 | ), |
474 | &proc_res, | |
475 | ); | |
a7813a04 XL |
476 | } |
477 | ||
ff7c6d11 | 478 | let ProcRes { stdout, .. } = proc_res; |
a7813a04 XL |
479 | srcs.push(stdout); |
480 | round += 1; | |
481 | } | |
482 | ||
483 | let mut expected = match self.props.pp_exact { | |
484 | Some(ref file) => { | |
485 | let filepath = self.testpaths.file.parent().unwrap().join(file); | |
0731742a | 486 | fs::read_to_string(&filepath).unwrap() |
a7813a04 | 487 | } |
ff7c6d11 | 488 | None => srcs[srcs.len() - 2].clone(), |
a7813a04 XL |
489 | }; |
490 | let mut actual = srcs[srcs.len() - 1].clone(); | |
491 | ||
492 | if self.props.pp_exact.is_some() { | |
493 | // Now we have to care about line endings | |
494 | let cr = "\r".to_owned(); | |
3dfed10e XL |
495 | actual = actual.replace(&cr, ""); |
496 | expected = expected.replace(&cr, ""); | |
a7813a04 XL |
497 | } |
498 | ||
5099ac24 FG |
499 | if !self.config.bless { |
500 | self.compare_source(&expected, &actual); | |
501 | } else if expected != actual { | |
502 | let filepath_buf; | |
503 | let filepath = match &self.props.pp_exact { | |
504 | Some(file) => { | |
505 | filepath_buf = self.testpaths.file.parent().unwrap().join(file); | |
506 | &filepath_buf | |
507 | } | |
508 | None => &self.testpaths.file, | |
509 | }; | |
510 | fs::write(filepath, &actual).unwrap(); | |
511 | } | |
a7813a04 XL |
512 | |
513 | // If we're only making sure that the output matches then just stop here | |
ff7c6d11 XL |
514 | if self.props.pretty_compare_only { |
515 | return; | |
516 | } | |
a7813a04 XL |
517 | |
518 | // Finally, let's make sure it actually appears to remain valid code | |
519 | let proc_res = self.typecheck_source(actual); | |
520 | if !proc_res.status.success() { | |
521 | self.fatal_proc_rec("pretty-printed source does not typecheck", &proc_res); | |
522 | } | |
523 | ||
ff7c6d11 XL |
524 | if !self.props.pretty_expanded { |
525 | return; | |
526 | } | |
a7813a04 | 527 | |
94222f64 | 528 | // additionally, run `-Zunpretty=expanded` and try to build it. |
0bf4aa26 | 529 | let proc_res = self.print_source(ReadFrom::Path, "expanded"); |
a7813a04 XL |
530 | if !proc_res.status.success() { |
531 | self.fatal_proc_rec("pretty-printing (expanded) failed", &proc_res); | |
532 | } | |
533 | ||
dfeec247 | 534 | let ProcRes { stdout: expanded_src, .. } = proc_res; |
a7813a04 XL |
535 | let proc_res = self.typecheck_source(expanded_src); |
536 | if !proc_res.status.success() { | |
dfeec247 | 537 | self.fatal_proc_rec("pretty-printed source (expanded) does not typecheck", &proc_res); |
a7813a04 XL |
538 | } |
539 | } | |
540 | ||
0bf4aa26 | 541 | fn print_source(&self, read_from: ReadFrom, pretty_type: &str) -> ProcRes { |
a7813a04 | 542 | let aux_dir = self.aux_output_dir_name(); |
0bf4aa26 XL |
543 | let input: &str = match read_from { |
544 | ReadFrom::Stdin(_) => "-", | |
545 | ReadFrom::Path => self.testpaths.file.to_str().unwrap(), | |
546 | }; | |
3b2f2976 XL |
547 | |
548 | let mut rustc = Command::new(&self.config.rustc_path); | |
ff7c6d11 | 549 | rustc |
0bf4aa26 | 550 | .arg(input) |
2c00a5a8 | 551 | .args(&["-Z", &format!("unpretty={}", pretty_type)]) |
3b2f2976 | 552 | .args(&["--target", &self.config.target]) |
ff7c6d11 XL |
553 | .arg("-L") |
554 | .arg(&aux_dir) | |
3b2f2976 | 555 | .args(&self.props.compile_flags) |
3dfed10e | 556 | .envs(self.props.rustc_env.clone()); |
2b03887a | 557 | self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags); |
3b2f2976 | 558 | |
0bf4aa26 XL |
559 | let src = match read_from { |
560 | ReadFrom::Stdin(src) => Some(src), | |
dfeec247 | 561 | ReadFrom::Path => None, |
0bf4aa26 XL |
562 | }; |
563 | ||
ff7c6d11 XL |
564 | self.compose_and_run( |
565 | rustc, | |
566 | self.config.compile_lib_path.to_str().unwrap(), | |
567 | Some(aux_dir.to_str().unwrap()), | |
0bf4aa26 | 568 | src, |
ff7c6d11 | 569 | ) |
a7813a04 XL |
570 | } |
571 | ||
ff7c6d11 | 572 | fn compare_source(&self, expected: &str, actual: &str) { |
a7813a04 | 573 | if expected != actual { |
8faf50e0 XL |
574 | self.fatal(&format!( |
575 | "pretty-printed source does not match expected source\n\ | |
ff7c6d11 XL |
576 | expected:\n\ |
577 | ------------------------------------------\n\ | |
578 | {}\n\ | |
579 | ------------------------------------------\n\ | |
580 | actual:\n\ | |
581 | ------------------------------------------\n\ | |
582 | {}\n\ | |
583 | ------------------------------------------\n\ | |
3dfed10e XL |
584 | diff:\n\ |
585 | ------------------------------------------\n\ | |
586 | {}\n", | |
587 | expected, | |
588 | actual, | |
589 | write_diff(expected, actual, 3), | |
dfeec247 | 590 | )); |
a7813a04 XL |
591 | } |
592 | } | |
593 | ||
9fa01778 XL |
594 | fn set_revision_flags(&self, cmd: &mut Command) { |
595 | if let Some(revision) = self.revision { | |
596 | // Normalize revisions to be lowercase and replace `-`s with `_`s. | |
597 | // Otherwise the `--cfg` flag is not valid. | |
598 | let normalized_revision = revision.to_lowercase().replace("-", "_"); | |
599 | cmd.args(&["--cfg", &normalized_revision]); | |
600 | } | |
601 | } | |
602 | ||
a7813a04 | 603 | fn typecheck_source(&self, src: String) -> ProcRes { |
3b2f2976 XL |
604 | let mut rustc = Command::new(&self.config.rustc_path); |
605 | ||
606 | let out_dir = self.output_base_name().with_extension("pretty-out"); | |
607 | let _ = fs::remove_dir_all(&out_dir); | |
608 | create_dir_all(&out_dir).unwrap(); | |
a7813a04 | 609 | |
dfeec247 | 610 | let target = if self.props.force_host { &*self.config.host } else { &*self.config.target }; |
3157f602 | 611 | |
3b2f2976 XL |
612 | let aux_dir = self.aux_output_dir_name(); |
613 | ||
ff7c6d11 XL |
614 | rustc |
615 | .arg("-") | |
94b46f34 | 616 | .arg("-Zno-codegen") |
ff7c6d11 XL |
617 | .arg("--out-dir") |
618 | .arg(&out_dir) | |
3b2f2976 | 619 | .arg(&format!("--target={}", target)) |
ff7c6d11 XL |
620 | .arg("-L") |
621 | .arg(&self.config.build_base) | |
622 | .arg("-L") | |
623 | .arg(aux_dir); | |
9fa01778 | 624 | self.set_revision_flags(&mut rustc); |
2b03887a | 625 | self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags); |
3b2f2976 XL |
626 | rustc.args(&self.props.compile_flags); |
627 | ||
628 | self.compose_and_run_compiler(rustc, Some(src)) | |
a7813a04 XL |
629 | } |
630 | ||
dfeec247 XL |
631 | fn run_debuginfo_test(&self) { |
632 | match self.config.debugger.unwrap() { | |
633 | Debugger::Cdb => self.run_debuginfo_cdb_test(), | |
634 | Debugger::Gdb => self.run_debuginfo_gdb_test(), | |
635 | Debugger::Lldb => self.run_debuginfo_lldb_test(), | |
636 | } | |
637 | } | |
638 | ||
dc9dc135 | 639 | fn run_debuginfo_cdb_test(&self) { |
dc9dc135 XL |
640 | let config = Config { |
641 | target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), | |
642 | host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), | |
dc9dc135 XL |
643 | ..self.config.clone() |
644 | }; | |
645 | ||
dfeec247 | 646 | let test_cx = TestCx { config: &config, ..*self }; |
dc9dc135 XL |
647 | |
648 | test_cx.run_debuginfo_cdb_test_no_opt(); | |
649 | } | |
650 | ||
651 | fn run_debuginfo_cdb_test_no_opt(&self) { | |
5e7ed085 FG |
652 | let exe_file = self.make_exe_name(); |
653 | ||
654 | // Existing PDB files are update in-place. When changing the debuginfo | |
655 | // the compiler generates for something, this can lead to the situation | |
656 | // where both the old and the new version of the debuginfo for the same | |
657 | // type is present in the PDB, which is very confusing. | |
658 | // Therefore we delete any existing PDB file before compiling the test | |
659 | // case. | |
660 | // FIXME: If can reliably detect that MSVC's link.exe is used, then | |
661 | // passing `/INCREMENTAL:NO` might be a cleaner way to do this. | |
662 | let pdb_file = exe_file.with_extension(".pdb"); | |
663 | if pdb_file.exists() { | |
664 | std::fs::remove_file(pdb_file).unwrap(); | |
665 | } | |
666 | ||
dc9dc135 | 667 | // compile test file (it should have 'compile-flags:-g' in the header) |
17df50a5 | 668 | let should_run = self.run_if_enabled(); |
487cf647 | 669 | let compile_result = self.compile_test(should_run, Emit::None); |
dc9dc135 XL |
670 | if !compile_result.status.success() { |
671 | self.fatal_proc_rec("compilation failed!", &compile_result); | |
672 | } | |
17df50a5 XL |
673 | if let WillExecute::Disabled = should_run { |
674 | return; | |
675 | } | |
dc9dc135 | 676 | |
dc9dc135 | 677 | let prefixes = { |
3dfed10e | 678 | static PREFIXES: &[&str] = &["cdb", "cdbg"]; |
dc9dc135 XL |
679 | // No "native rust support" variation for CDB yet. |
680 | PREFIXES | |
681 | }; | |
682 | ||
683 | // Parse debugger commands etc from test files | |
dfeec247 | 684 | let DebuggerCommands { commands, check_lines, breakpoint_lines, .. } = |
064997fb FG |
685 | match DebuggerCommands::parse_from( |
686 | &self.testpaths.file, | |
687 | self.config, | |
688 | prefixes, | |
689 | self.revision, | |
690 | ) { | |
c295e0f8 XL |
691 | Ok(cmds) => cmds, |
692 | Err(e) => self.fatal(&e), | |
693 | }; | |
dc9dc135 XL |
694 | |
695 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands | |
696 | let mut script_str = String::with_capacity(2048); | |
697 | script_str.push_str("version\n"); // List CDB (and more) version info in test output | |
698 | script_str.push_str(".nvlist\n"); // List loaded `*.natvis` files, bulk of custom MSVC debug | |
699 | ||
136023e0 XL |
700 | // If a .js file exists next to the source file being tested, then this is a JavaScript |
701 | // debugging extension that needs to be loaded. | |
702 | let mut js_extension = self.testpaths.file.clone(); | |
703 | js_extension.set_extension("cdb.js"); | |
704 | if js_extension.exists() { | |
705 | script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension.to_string_lossy())); | |
706 | } | |
707 | ||
dc9dc135 XL |
708 | // Set breakpoints on every line that contains the string "#break" |
709 | let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); | |
710 | for line in &breakpoint_lines { | |
dfeec247 | 711 | script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line)); |
dc9dc135 XL |
712 | } |
713 | ||
714 | // Append the other `cdb-command:`s | |
715 | for line in &commands { | |
716 | script_str.push_str(line); | |
717 | script_str.push_str("\n"); | |
718 | } | |
719 | ||
487cf647 | 720 | script_str.push_str("qq\n"); // Quit the debugger (including remote debugger, if any) |
dc9dc135 XL |
721 | |
722 | // Write the script into a file | |
723 | debug!("script_str = {}", script_str); | |
724 | self.dump_output_file(&script_str, "debugger.script"); | |
725 | let debugger_script = self.make_out_name("debugger.script"); | |
726 | ||
727 | let cdb_path = &self.config.cdb.as_ref().unwrap(); | |
728 | let mut cdb = Command::new(cdb_path); | |
dfeec247 XL |
729 | cdb.arg("-lines") // Enable source line debugging. |
730 | .arg("-cf") | |
731 | .arg(&debugger_script) | |
dc9dc135 XL |
732 | .arg(&exe_file); |
733 | ||
734 | let debugger_run_result = self.compose_and_run( | |
735 | cdb, | |
736 | self.config.run_lib_path.to_str().unwrap(), | |
737 | None, // aux_path | |
dfeec247 | 738 | None, // input |
dc9dc135 XL |
739 | ); |
740 | ||
741 | if !debugger_run_result.status.success() { | |
742 | self.fatal_proc_rec("Error while running CDB", &debugger_run_result); | |
743 | } | |
744 | ||
c295e0f8 XL |
745 | if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { |
746 | self.fatal_proc_rec(&e, &debugger_run_result); | |
747 | } | |
dc9dc135 XL |
748 | } |
749 | ||
a7813a04 | 750 | fn run_debuginfo_gdb_test(&self) { |
a7813a04 XL |
751 | let config = Config { |
752 | target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), | |
753 | host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), | |
ff7c6d11 | 754 | ..self.config.clone() |
a7813a04 XL |
755 | }; |
756 | ||
dfeec247 | 757 | let test_cx = TestCx { config: &config, ..*self }; |
a7813a04 XL |
758 | |
759 | test_cx.run_debuginfo_gdb_test_no_opt(); | |
760 | } | |
761 | ||
762 | fn run_debuginfo_gdb_test_no_opt(&self) { | |
c30ab7b3 SL |
763 | let prefixes = if self.config.gdb_native_rust { |
764 | // GDB with Rust | |
3dfed10e | 765 | static PREFIXES: &[&str] = &["gdb", "gdbr"]; |
c30ab7b3 SL |
766 | println!("NOTE: compiletest thinks it is using GDB with native rust support"); |
767 | PREFIXES | |
768 | } else { | |
769 | // Generic GDB | |
3dfed10e | 770 | static PREFIXES: &[&str] = &["gdb", "gdbg"]; |
c30ab7b3 SL |
771 | println!("NOTE: compiletest thinks it is using GDB without native rust support"); |
772 | PREFIXES | |
773 | }; | |
774 | ||
dfeec247 | 775 | let DebuggerCommands { commands, check_lines, breakpoint_lines } = |
064997fb FG |
776 | match DebuggerCommands::parse_from( |
777 | &self.testpaths.file, | |
778 | self.config, | |
779 | prefixes, | |
780 | self.revision, | |
781 | ) { | |
c295e0f8 XL |
782 | Ok(cmds) => cmds, |
783 | Err(e) => self.fatal(&e), | |
784 | }; | |
a7813a04 XL |
785 | let mut cmds = commands.join("\n"); |
786 | ||
787 | // compile test file (it should have 'compile-flags:-g' in the header) | |
17df50a5 | 788 | let should_run = self.run_if_enabled(); |
487cf647 | 789 | let compiler_run_result = self.compile_test(should_run, Emit::None); |
a7813a04 XL |
790 | if !compiler_run_result.status.success() { |
791 | self.fatal_proc_rec("compilation failed!", &compiler_run_result); | |
792 | } | |
17df50a5 XL |
793 | if let WillExecute::Disabled = should_run { |
794 | return; | |
795 | } | |
a7813a04 XL |
796 | |
797 | let exe_file = self.make_exe_name(); | |
798 | ||
799 | let debugger_run_result; | |
0bf4aa26 XL |
800 | if is_android_gdb_target(&self.config.target) { |
801 | cmds = cmds.replace("run", "continue"); | |
a7813a04 | 802 | |
0bf4aa26 XL |
803 | let tool_path = match self.config.android_cross_path.to_str() { |
804 | Some(x) => x.to_owned(), | |
805 | None => self.fatal("cannot find android cross path"), | |
806 | }; | |
a7813a04 | 807 | |
0bf4aa26 XL |
808 | // write debugger script |
809 | let mut script_str = String::with_capacity(2048); | |
810 | script_str.push_str(&format!("set charset {}\n", Self::charset())); | |
811 | script_str.push_str(&format!("set sysroot {}\n", tool_path)); | |
812 | script_str.push_str(&format!("file {}\n", exe_file.to_str().unwrap())); | |
813 | script_str.push_str("target remote :5039\n"); | |
814 | script_str.push_str(&format!( | |
815 | "set solib-search-path \ | |
816 | ./{}/stage2/lib/rustlib/{}/lib/\n", | |
817 | self.config.host, self.config.target | |
818 | )); | |
819 | for line in &breakpoint_lines { | |
820 | script_str.push_str( | |
821 | &format!( | |
822 | "break {:?}:{}\n", | |
823 | self.testpaths.file.file_name().unwrap().to_string_lossy(), | |
824 | *line | |
825 | )[..], | |
ff7c6d11 | 826 | ); |
0bf4aa26 XL |
827 | } |
828 | script_str.push_str(&cmds); | |
829 | script_str.push_str("\nquit\n"); | |
830 | ||
831 | debug!("script_str = {}", script_str); | |
832 | self.dump_output_file(&script_str, "debugger.script"); | |
833 | ||
834 | let adb_path = &self.config.adb_path; | |
835 | ||
836 | Command::new(adb_path) | |
837 | .arg("push") | |
838 | .arg(&exe_file) | |
839 | .arg(&self.config.adb_test_dir) | |
840 | .status() | |
3dfed10e | 841 | .unwrap_or_else(|_| panic!("failed to exec `{:?}`", adb_path)); |
0bf4aa26 XL |
842 | |
843 | Command::new(adb_path) | |
844 | .args(&["forward", "tcp:5039", "tcp:5039"]) | |
845 | .status() | |
3dfed10e | 846 | .unwrap_or_else(|_| panic!("failed to exec `{:?}`", adb_path)); |
0bf4aa26 XL |
847 | |
848 | let adb_arg = format!( | |
849 | "export LD_LIBRARY_PATH={}; \ | |
850 | gdbserver{} :5039 {}/{}", | |
851 | self.config.adb_test_dir.clone(), | |
dfeec247 | 852 | if self.config.target.contains("aarch64") { "64" } else { "" }, |
0bf4aa26 XL |
853 | self.config.adb_test_dir.clone(), |
854 | exe_file.file_name().unwrap().to_str().unwrap() | |
855 | ); | |
a7813a04 | 856 | |
0bf4aa26 XL |
857 | debug!("adb arg: {}", adb_arg); |
858 | let mut adb = Command::new(adb_path) | |
859 | .args(&["shell", &adb_arg]) | |
860 | .stdout(Stdio::piped()) | |
861 | .stderr(Stdio::inherit()) | |
862 | .spawn() | |
3dfed10e | 863 | .unwrap_or_else(|_| panic!("failed to exec `{:?}`", adb_path)); |
0bf4aa26 XL |
864 | |
865 | // Wait for the gdbserver to print out "Listening on port ..." | |
866 | // at which point we know that it's started and then we can | |
867 | // execute the debugger below. | |
868 | let mut stdout = BufReader::new(adb.stdout.take().unwrap()); | |
869 | let mut line = String::new(); | |
870 | loop { | |
871 | line.truncate(0); | |
872 | stdout.read_line(&mut line).unwrap(); | |
873 | if line.starts_with("Listening on port 5039") { | |
874 | break; | |
a7813a04 | 875 | } |
0bf4aa26 XL |
876 | } |
877 | drop(stdout); | |
878 | ||
0731742a XL |
879 | let mut debugger_script = OsString::from("-command="); |
880 | debugger_script.push(self.make_out_name("debugger.script")); | |
dfeec247 XL |
881 | let debugger_opts: &[&OsStr] = |
882 | &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script]; | |
0bf4aa26 XL |
883 | |
884 | let gdb_path = self.config.gdb.as_ref().unwrap(); | |
dfeec247 | 885 | let Output { status, stdout, stderr } = Command::new(&gdb_path) |
0731742a | 886 | .args(debugger_opts) |
0bf4aa26 | 887 | .output() |
3dfed10e | 888 | .unwrap_or_else(|_| panic!("failed to exec `{:?}`", gdb_path)); |
0bf4aa26 XL |
889 | let cmdline = { |
890 | let mut gdb = Command::new(&format!("{}-gdb", self.config.target)); | |
0731742a | 891 | gdb.args(debugger_opts); |
0bf4aa26 XL |
892 | let cmdline = self.make_cmdline(&gdb, ""); |
893 | logv(self.config, format!("executing {}", cmdline)); | |
894 | cmdline | |
895 | }; | |
a7813a04 | 896 | |
0bf4aa26 XL |
897 | debugger_run_result = ProcRes { |
898 | status, | |
899 | stdout: String::from_utf8(stdout).unwrap(), | |
900 | stderr: String::from_utf8(stderr).unwrap(), | |
901 | cmdline, | |
902 | }; | |
903 | if adb.kill().is_err() { | |
904 | println!("Adb process is already finished."); | |
a7813a04 | 905 | } |
0bf4aa26 | 906 | } else { |
dfeec247 XL |
907 | let rust_src_root = |
908 | self.config.find_rust_src_root().expect("Could not find Rust source root"); | |
0bf4aa26 | 909 | let rust_pp_module_rel_path = Path::new("./src/etc"); |
dfeec247 XL |
910 | let rust_pp_module_abs_path = |
911 | rust_src_root.join(rust_pp_module_rel_path).to_str().unwrap().to_owned(); | |
0bf4aa26 XL |
912 | // write debugger script |
913 | let mut script_str = String::with_capacity(2048); | |
914 | script_str.push_str(&format!("set charset {}\n", Self::charset())); | |
915 | script_str.push_str("show version\n"); | |
916 | ||
917 | match self.config.gdb_version { | |
918 | Some(version) => { | |
dfeec247 | 919 | println!("NOTE: compiletest thinks it is using GDB version {}", version); |
a7813a04 | 920 | |
0bf4aa26 XL |
921 | if version > extract_gdb_version("7.4").unwrap() { |
922 | // Add the directory containing the pretty printers to | |
923 | // GDB's script auto loading safe path | |
924 | script_str.push_str(&format!( | |
925 | "add-auto-load-safe-path {}\n", | |
926 | rust_pp_module_abs_path.replace(r"\", r"\\") | |
927 | )); | |
923072b8 FG |
928 | |
929 | let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned(); | |
930 | ||
931 | // Add the directory containing the output binary to | |
932 | // include embedded pretty printers to GDB's script | |
933 | // auto loading safe path | |
934 | script_str.push_str(&format!( | |
935 | "add-auto-load-safe-path {}\n", | |
936 | output_base_dir.replace(r"\", r"\\") | |
937 | )); | |
a7813a04 XL |
938 | } |
939 | } | |
0bf4aa26 XL |
940 | _ => { |
941 | println!( | |
942 | "NOTE: compiletest does not know which version of \ | |
943 | GDB it is using" | |
944 | ); | |
945 | } | |
946 | } | |
a7813a04 | 947 | |
0bf4aa26 XL |
948 | // The following line actually doesn't have to do anything with |
949 | // pretty printing, it just tells GDB to print values on one line: | |
950 | script_str.push_str("set print pretty off\n"); | |
a7813a04 | 951 | |
0bf4aa26 | 952 | // Add the pretty printer directory to GDB's source-file search path |
fc512014 XL |
953 | script_str |
954 | .push_str(&format!("directory {}\n", rust_pp_module_abs_path.replace(r"\", r"\\"))); | |
a7813a04 | 955 | |
0bf4aa26 | 956 | // Load the target executable |
dfeec247 XL |
957 | script_str |
958 | .push_str(&format!("file {}\n", exe_file.to_str().unwrap().replace(r"\", r"\\"))); | |
a7813a04 | 959 | |
0bf4aa26 XL |
960 | // Force GDB to print values in the Rust format. |
961 | if self.config.gdb_native_rust { | |
962 | script_str.push_str("set language rust\n"); | |
963 | } | |
7cac9316 | 964 | |
0bf4aa26 XL |
965 | // Add line breakpoints |
966 | for line in &breakpoint_lines { | |
967 | script_str.push_str(&format!( | |
968 | "break '{}':{}\n", | |
969 | self.testpaths.file.file_name().unwrap().to_string_lossy(), | |
970 | *line | |
971 | )); | |
972 | } | |
a7813a04 | 973 | |
0bf4aa26 XL |
974 | script_str.push_str(&cmds); |
975 | script_str.push_str("\nquit\n"); | |
a7813a04 | 976 | |
0bf4aa26 XL |
977 | debug!("script_str = {}", script_str); |
978 | self.dump_output_file(&script_str, "debugger.script"); | |
a7813a04 | 979 | |
0731742a XL |
980 | let mut debugger_script = OsString::from("-command="); |
981 | debugger_script.push(self.make_out_name("debugger.script")); | |
a7813a04 | 982 | |
dfeec247 XL |
983 | let debugger_opts: &[&OsStr] = |
984 | &["-quiet".as_ref(), "-batch".as_ref(), "-nx".as_ref(), &debugger_script]; | |
a7813a04 | 985 | |
0bf4aa26 | 986 | let mut gdb = Command::new(self.config.gdb.as_ref().unwrap()); |
dfeec247 XL |
987 | gdb.args(debugger_opts).env("PYTHONPATH", rust_pp_module_abs_path); |
988 | ||
989 | debugger_run_result = | |
990 | self.compose_and_run(gdb, self.config.run_lib_path.to_str().unwrap(), None, None); | |
a7813a04 XL |
991 | } |
992 | ||
993 | if !debugger_run_result.status.success() { | |
041b39d2 | 994 | self.fatal_proc_rec("gdb failed to execute", &debugger_run_result); |
a7813a04 XL |
995 | } |
996 | ||
c295e0f8 XL |
997 | if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { |
998 | self.fatal_proc_rec(&e, &debugger_run_result); | |
999 | } | |
a7813a04 XL |
1000 | } |
1001 | ||
a7813a04 | 1002 | fn run_debuginfo_lldb_test(&self) { |
a7813a04 XL |
1003 | if self.config.lldb_python_dir.is_none() { |
1004 | self.fatal("Can't run LLDB test because LLDB's python path is not set."); | |
1005 | } | |
1006 | ||
1007 | let config = Config { | |
1008 | target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), | |
1009 | host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), | |
ff7c6d11 | 1010 | ..self.config.clone() |
a7813a04 XL |
1011 | }; |
1012 | ||
dfeec247 | 1013 | let test_cx = TestCx { config: &config, ..*self }; |
a7813a04 XL |
1014 | |
1015 | test_cx.run_debuginfo_lldb_test_no_opt(); | |
1016 | } | |
1017 | ||
1018 | fn run_debuginfo_lldb_test_no_opt(&self) { | |
1019 | // compile test file (it should have 'compile-flags:-g' in the header) | |
17df50a5 | 1020 | let should_run = self.run_if_enabled(); |
487cf647 | 1021 | let compile_result = self.compile_test(should_run, Emit::None); |
a7813a04 XL |
1022 | if !compile_result.status.success() { |
1023 | self.fatal_proc_rec("compilation failed!", &compile_result); | |
1024 | } | |
17df50a5 XL |
1025 | if let WillExecute::Disabled = should_run { |
1026 | return; | |
1027 | } | |
a7813a04 XL |
1028 | |
1029 | let exe_file = self.make_exe_name(); | |
1030 | ||
1031 | match self.config.lldb_version { | |
1032 | Some(ref version) => { | |
dfeec247 | 1033 | println!("NOTE: compiletest thinks it is using LLDB version {}", version); |
a7813a04 XL |
1034 | } |
1035 | _ => { | |
ff7c6d11 XL |
1036 | println!( |
1037 | "NOTE: compiletest does not know which version of \ | |
1038 | LLDB it is using" | |
1039 | ); | |
a7813a04 XL |
1040 | } |
1041 | } | |
1042 | ||
0bf4aa26 | 1043 | let prefixes = if self.config.lldb_native_rust { |
3dfed10e | 1044 | static PREFIXES: &[&str] = &["lldb", "lldbr"]; |
0bf4aa26 XL |
1045 | println!("NOTE: compiletest thinks it is using LLDB with native rust support"); |
1046 | PREFIXES | |
1047 | } else { | |
3dfed10e | 1048 | static PREFIXES: &[&str] = &["lldb", "lldbg"]; |
0bf4aa26 XL |
1049 | println!("NOTE: compiletest thinks it is using LLDB without native rust support"); |
1050 | PREFIXES | |
1051 | }; | |
1052 | ||
a7813a04 | 1053 | // Parse debugger commands etc from test files |
dfeec247 | 1054 | let DebuggerCommands { commands, check_lines, breakpoint_lines, .. } = |
064997fb FG |
1055 | match DebuggerCommands::parse_from( |
1056 | &self.testpaths.file, | |
1057 | self.config, | |
1058 | prefixes, | |
1059 | self.revision, | |
1060 | ) { | |
c295e0f8 XL |
1061 | Ok(cmds) => cmds, |
1062 | Err(e) => self.fatal(&e), | |
1063 | }; | |
a7813a04 XL |
1064 | |
1065 | // Write debugger script: | |
1066 | // We don't want to hang when calling `quit` while the process is still running | |
1067 | let mut script_str = String::from("settings set auto-confirm true\n"); | |
1068 | ||
1069 | // Make LLDB emit its version, so we have it documented in the test output | |
1070 | script_str.push_str("version\n"); | |
1071 | ||
1072 | // Switch LLDB into "Rust mode" | |
dfeec247 XL |
1073 | let rust_src_root = |
1074 | self.config.find_rust_src_root().expect("Could not find Rust source root"); | |
f035d41b | 1075 | let rust_pp_module_rel_path = Path::new("./src/etc/lldb_lookup.py"); |
dfeec247 XL |
1076 | let rust_pp_module_abs_path = |
1077 | rust_src_root.join(rust_pp_module_rel_path).to_str().unwrap().to_owned(); | |
ff7c6d11 | 1078 | |
f035d41b XL |
1079 | let rust_type_regexes = vec![ |
1080 | "^(alloc::([a-z_]+::)+)String$", | |
136023e0 XL |
1081 | "^&(mut )?str$", |
1082 | "^&(mut )?\\[.+\\]$", | |
f035d41b XL |
1083 | "^(std::ffi::([a-z_]+::)+)OsString$", |
1084 | "^(alloc::([a-z_]+::)+)Vec<.+>$", | |
1085 | "^(alloc::([a-z_]+::)+)VecDeque<.+>$", | |
1086 | "^(alloc::([a-z_]+::)+)BTreeSet<.+>$", | |
1087 | "^(alloc::([a-z_]+::)+)BTreeMap<.+>$", | |
1088 | "^(std::collections::([a-z_]+::)+)HashMap<.+>$", | |
1089 | "^(std::collections::([a-z_]+::)+)HashSet<.+>$", | |
1090 | "^(alloc::([a-z_]+::)+)Rc<.+>$", | |
1091 | "^(alloc::([a-z_]+::)+)Arc<.+>$", | |
1092 | "^(core::([a-z_]+::)+)Cell<.+>$", | |
1093 | "^(core::([a-z_]+::)+)Ref<.+>$", | |
1094 | "^(core::([a-z_]+::)+)RefMut<.+>$", | |
1095 | "^(core::([a-z_]+::)+)RefCell<.+>$", | |
f2b60f7d | 1096 | "^core::num::([a-z_]+::)*NonZero.+$", |
f035d41b XL |
1097 | ]; |
1098 | ||
ff7c6d11 XL |
1099 | script_str |
1100 | .push_str(&format!("command script import {}\n", &rust_pp_module_abs_path[..])[..]); | |
f035d41b XL |
1101 | script_str.push_str("type synthetic add -l lldb_lookup.synthetic_lookup -x '.*' "); |
1102 | script_str.push_str("--category Rust\n"); | |
1103 | for type_regex in rust_type_regexes { | |
1104 | script_str.push_str("type summary add -F lldb_lookup.summary_lookup -e -x -h "); | |
1105 | script_str.push_str(&format!("'{}' ", type_regex)); | |
1106 | script_str.push_str("--category Rust\n"); | |
1107 | } | |
a7813a04 XL |
1108 | script_str.push_str("type category enable Rust\n"); |
1109 | ||
1110 | // Set breakpoints on every line that contains the string "#break" | |
1111 | let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); | |
1112 | for line in &breakpoint_lines { | |
ff7c6d11 XL |
1113 | script_str.push_str(&format!( |
1114 | "breakpoint set --file '{}' --line {}\n", | |
94b46f34 | 1115 | source_file_name, line |
ff7c6d11 | 1116 | )); |
a7813a04 XL |
1117 | } |
1118 | ||
1119 | // Append the other commands | |
1120 | for line in &commands { | |
1121 | script_str.push_str(line); | |
1122 | script_str.push_str("\n"); | |
1123 | } | |
1124 | ||
1125 | // Finally, quit the debugger | |
1126 | script_str.push_str("\nquit\n"); | |
1127 | ||
1128 | // Write the script into a file | |
1129 | debug!("script_str = {}", script_str); | |
1130 | self.dump_output_file(&script_str, "debugger.script"); | |
1131 | let debugger_script = self.make_out_name("debugger.script"); | |
1132 | ||
1133 | // Let LLDB execute the script via lldb_batchmode.py | |
ff7c6d11 | 1134 | let debugger_run_result = self.run_lldb(&exe_file, &debugger_script, &rust_src_root); |
a7813a04 XL |
1135 | |
1136 | if !debugger_run_result.status.success() { | |
1137 | self.fatal_proc_rec("Error while running LLDB", &debugger_run_result); | |
1138 | } | |
1139 | ||
c295e0f8 XL |
1140 | if let Err(e) = check_debugger_output(&debugger_run_result, &check_lines) { |
1141 | self.fatal_proc_rec(&e, &debugger_run_result); | |
1142 | } | |
a7813a04 XL |
1143 | } |
1144 | ||
ff7c6d11 XL |
1145 | fn run_lldb( |
1146 | &self, | |
1147 | test_executable: &Path, | |
1148 | debugger_script: &Path, | |
1149 | rust_src_root: &Path, | |
1150 | ) -> ProcRes { | |
a7813a04 XL |
1151 | // Prepare the lldb_batchmode which executes the debugger script |
1152 | let lldb_script_path = rust_src_root.join("src/etc/lldb_batchmode.py"); | |
ff7c6d11 | 1153 | self.cmd2procres( |
04454e1e | 1154 | Command::new(&self.config.python) |
ff7c6d11 XL |
1155 | .arg(&lldb_script_path) |
1156 | .arg(test_executable) | |
1157 | .arg(debugger_script) | |
29967ef6 | 1158 | .env("PYTHONUNBUFFERED", "1") // Help debugging #78665 |
ff7c6d11 XL |
1159 | .env("PYTHONPATH", self.config.lldb_python_dir.as_ref().unwrap()), |
1160 | ) | |
a7813a04 XL |
1161 | } |
1162 | ||
1163 | fn cmd2procres(&self, cmd: &mut Command) -> ProcRes { | |
1164 | let (status, out, err) = match cmd.output() { | |
dfeec247 XL |
1165 | Ok(Output { status, stdout, stderr }) => { |
1166 | (status, String::from_utf8(stdout).unwrap(), String::from_utf8(stderr).unwrap()) | |
1167 | } | |
ff7c6d11 XL |
1168 | Err(e) => self.fatal(&format!( |
1169 | "Failed to setup Python process for \ | |
1170 | LLDB script: {}", | |
1171 | e | |
1172 | )), | |
a7813a04 XL |
1173 | }; |
1174 | ||
1175 | self.dump_output(&out, &err); | |
dfeec247 | 1176 | ProcRes { status, stdout: out, stderr: err, cmdline: format!("{:?}", cmd) } |
a7813a04 XL |
1177 | } |
1178 | ||
2b03887a | 1179 | fn cleanup_debug_info_options(&self, options: &Vec<String>) -> Vec<String> { |
a7813a04 | 1180 | // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS. |
ff7c6d11 | 1181 | let options_to_remove = ["-O".to_owned(), "-g".to_owned(), "--debuginfo".to_owned()]; |
a7813a04 | 1182 | |
2b03887a | 1183 | options.iter().filter(|x| !options_to_remove.contains(x)).map(|x| x.clone()).collect() |
a7813a04 XL |
1184 | } |
1185 | ||
2b03887a | 1186 | fn maybe_add_external_args(&self, cmd: &mut Command, args: &Vec<String>) { |
9fa01778 XL |
1187 | // Filter out the arguments that should not be added by runtest here. |
1188 | // | |
1189 | // Notable use-cases are: do not add our optimisation flag if | |
1190 | // `compile-flags: -Copt-level=x` and similar for debug-info level as well. | |
dfeec247 XL |
1191 | const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", /*-C<space>*/ "opt-level="]; |
1192 | const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", /*-C<space>*/ "debuginfo="]; | |
9fa01778 XL |
1193 | |
1194 | // FIXME: ideally we would "just" check the `cmd` itself, but it does not allow inspecting | |
1195 | // its arguments. They need to be collected separately. For now I cannot be bothered to | |
1196 | // implement this the "right" way. | |
dfeec247 XL |
1197 | let have_opt_flag = |
1198 | self.props.compile_flags.iter().any(|arg| OPT_FLAGS.iter().any(|f| arg.starts_with(f))); | |
1199 | let have_debug_flag = self | |
1200 | .props | |
1201 | .compile_flags | |
1202 | .iter() | |
1203 | .any(|arg| DEBUG_FLAGS.iter().any(|f| arg.starts_with(f))); | |
9fa01778 XL |
1204 | |
1205 | for arg in args { | |
1206 | if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag { | |
1207 | continue; | |
1208 | } | |
1209 | if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag { | |
1210 | continue; | |
1211 | } | |
1212 | cmd.arg(arg); | |
1213 | } | |
1214 | } | |
1215 | ||
064997fb | 1216 | fn check_all_error_patterns( |
dfeec247 XL |
1217 | &self, |
1218 | output_to_check: &str, | |
1219 | proc_res: &ProcRes, | |
1220 | pm: Option<PassMode>, | |
1221 | ) { | |
064997fb | 1222 | if self.props.error_patterns.is_empty() && self.props.regex_error_patterns.is_empty() { |
dfeec247 XL |
1223 | if pm.is_some() { |
1224 | // FIXME(#65865) | |
ff7c6d11 | 1225 | return; |
9e0c209e | 1226 | } else { |
ff7c6d11 XL |
1227 | self.fatal(&format!( |
1228 | "no error pattern specified in {:?}", | |
1229 | self.testpaths.file.display() | |
1230 | )); | |
9e0c209e | 1231 | } |
a7813a04 | 1232 | } |
b7449926 XL |
1233 | |
1234 | let mut missing_patterns: Vec<String> = Vec::new(); | |
1235 | ||
064997fb FG |
1236 | self.check_error_patterns(output_to_check, &mut missing_patterns); |
1237 | self.check_regex_error_patterns(output_to_check, proc_res, &mut missing_patterns); | |
b7449926 XL |
1238 | |
1239 | if missing_patterns.is_empty() { | |
ff7c6d11 XL |
1240 | return; |
1241 | } | |
a7813a04 | 1242 | |
a7813a04 XL |
1243 | if missing_patterns.len() == 1 { |
1244 | self.fatal_proc_rec( | |
1245 | &format!("error pattern '{}' not found!", missing_patterns[0]), | |
ff7c6d11 XL |
1246 | proc_res, |
1247 | ); | |
a7813a04 XL |
1248 | } else { |
1249 | for pattern in missing_patterns { | |
b7449926 | 1250 | self.error(&format!("error pattern '{}' not found!", pattern)); |
a7813a04 XL |
1251 | } |
1252 | self.fatal_proc_rec("multiple error patterns not found", proc_res); | |
1253 | } | |
1254 | } | |
1255 | ||
064997fb FG |
1256 | fn check_error_patterns(&self, output_to_check: &str, missing_patterns: &mut Vec<String>) { |
1257 | debug!("check_error_patterns"); | |
1258 | for pattern in &self.props.error_patterns { | |
1259 | if output_to_check.contains(pattern.trim()) { | |
1260 | debug!("found error pattern {}", pattern); | |
1261 | } else { | |
1262 | missing_patterns.push(pattern.to_string()); | |
1263 | } | |
1264 | } | |
1265 | } | |
1266 | ||
1267 | fn check_regex_error_patterns( | |
1268 | &self, | |
1269 | output_to_check: &str, | |
1270 | proc_res: &ProcRes, | |
1271 | missing_patterns: &mut Vec<String>, | |
1272 | ) { | |
1273 | debug!("check_regex_error_patterns"); | |
1274 | ||
1275 | for pattern in &self.props.regex_error_patterns { | |
1276 | let pattern = pattern.trim(); | |
1277 | let re = match Regex::new(pattern) { | |
1278 | Ok(re) => re, | |
1279 | Err(err) => { | |
1280 | self.fatal_proc_rec( | |
1281 | &format!("invalid regex error pattern '{}': {:?}", pattern, err), | |
1282 | proc_res, | |
1283 | ); | |
1284 | } | |
1285 | }; | |
1286 | if re.is_match(output_to_check) { | |
1287 | debug!("found regex error pattern {}", pattern); | |
1288 | } else { | |
1289 | missing_patterns.push(pattern.to_string()); | |
1290 | } | |
1291 | } | |
1292 | } | |
1293 | ||
60c5eb7d | 1294 | fn check_no_compiler_crash(&self, proc_res: &ProcRes, should_ice: bool) { |
8faf50e0 | 1295 | match proc_res.status.code() { |
60c5eb7d XL |
1296 | Some(101) if !should_ice => { |
1297 | self.fatal_proc_rec("compiler encountered internal error", proc_res) | |
1298 | } | |
8faf50e0 XL |
1299 | None => self.fatal_proc_rec("compiler terminated by signal", proc_res), |
1300 | _ => (), | |
a7813a04 XL |
1301 | } |
1302 | } | |
1303 | ||
ff7c6d11 | 1304 | fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) { |
a7813a04 XL |
1305 | for pat in &self.props.forbid_output { |
1306 | if output_to_check.contains(pat) { | |
1307 | self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res); | |
1308 | } | |
1309 | } | |
1310 | } | |
1311 | ||
ff7c6d11 | 1312 | fn check_expected_errors(&self, expected_errors: Vec<errors::Error>, proc_res: &ProcRes) { |
dfeec247 XL |
1313 | debug!( |
1314 | "check_expected_errors: expected_errors={:?} proc_res.status={:?}", | |
1315 | expected_errors, proc_res.status | |
1316 | ); | |
ff7c6d11 | 1317 | if proc_res.status.success() |
dfeec247 | 1318 | && expected_errors.iter().any(|x| x.kind == Some(ErrorKind::Error)) |
ff7c6d11 | 1319 | { |
a7813a04 XL |
1320 | self.fatal_proc_rec("process did not return an error status", proc_res); |
1321 | } | |
1322 | ||
5099ac24 FG |
1323 | if self.props.known_bug { |
1324 | if !expected_errors.is_empty() { | |
1325 | self.fatal_proc_rec( | |
1326 | "`known_bug` tests should not have an expected errors", | |
1327 | proc_res, | |
1328 | ); | |
1329 | } | |
1330 | return; | |
1331 | } | |
1332 | ||
9c376795 FG |
1333 | // On Windows, translate all '\' path separators to '/' |
1334 | let file_name = format!("{}", self.testpaths.file.display()).replace(r"\", "/"); | |
1335 | ||
8faf50e0 XL |
1336 | // On Windows, keep all '\' path separators to match the paths reported in the JSON output |
1337 | // from the compiler | |
9c376795 FG |
1338 | let diagnostic_file_name = if self.props.remap_src_base { |
1339 | let mut p = PathBuf::from(FAKE_SRC_BASE); | |
1340 | p.push(&self.testpaths.relative_dir); | |
1341 | p.push(self.testpaths.file.file_name().unwrap()); | |
1342 | p.display().to_string() | |
1343 | } else { | |
1344 | self.testpaths.file.display().to_string() | |
1345 | }; | |
a7813a04 XL |
1346 | |
1347 | // If the testcase being checked contains at least one expected "help" | |
1348 | // message, then we'll ensure that all "help" messages are expected. | |
1349 | // Otherwise, all "help" messages reported by the compiler will be ignored. | |
1350 | // This logic also applies to "note" messages. | |
dfeec247 XL |
1351 | let expect_help = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Help)); |
1352 | let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note)); | |
a7813a04 XL |
1353 | |
1354 | // Parse the JSON output from the compiler and extract out the messages. | |
9c376795 | 1355 | let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res); |
3157f602 | 1356 | let mut unexpected = Vec::new(); |
a7813a04 XL |
1357 | let mut found = vec![false; expected_errors.len()]; |
1358 | for actual_error in &actual_errors { | |
dfeec247 XL |
1359 | let opt_index = |
1360 | expected_errors.iter().enumerate().position(|(index, expected_error)| { | |
1361 | !found[index] | |
1362 | && actual_error.line_num == expected_error.line_num | |
ff7c6d11 XL |
1363 | && (expected_error.kind.is_none() |
1364 | || actual_error.kind == expected_error.kind) | |
1365 | && actual_error.msg.contains(&expected_error.msg) | |
dfeec247 | 1366 | }); |
a7813a04 XL |
1367 | |
1368 | match opt_index { | |
1369 | Some(index) => { | |
1370 | // found a match, everybody is happy | |
1371 | assert!(!found[index]); | |
1372 | found[index] = true; | |
1373 | } | |
1374 | ||
1375 | None => { | |
5099ac24 | 1376 | // If the test is a known bug, don't require that the error is annotated |
a7813a04 | 1377 | if self.is_unexpected_compiler_message(actual_error, expect_help, expect_note) { |
ff7c6d11 XL |
1378 | self.error(&format!( |
1379 | "{}:{}: unexpected {}: '{}'", | |
1380 | file_name, | |
1381 | actual_error.line_num, | |
1382 | actual_error | |
1383 | .kind | |
1384 | .as_ref() | |
1385 | .map_or(String::from("message"), |k| k.to_string()), | |
1386 | actual_error.msg | |
1387 | )); | |
041b39d2 | 1388 | unexpected.push(actual_error); |
a7813a04 XL |
1389 | } |
1390 | } | |
1391 | } | |
1392 | } | |
1393 | ||
3157f602 | 1394 | let mut not_found = Vec::new(); |
a7813a04 XL |
1395 | // anything not yet found is a problem |
1396 | for (index, expected_error) in expected_errors.iter().enumerate() { | |
1397 | if !found[index] { | |
ff7c6d11 XL |
1398 | self.error(&format!( |
1399 | "{}:{}: expected {} not found: {}", | |
1400 | file_name, | |
1401 | expected_error.line_num, | |
dfeec247 | 1402 | expected_error.kind.as_ref().map_or("message".into(), |k| k.to_string()), |
ff7c6d11 XL |
1403 | expected_error.msg |
1404 | )); | |
041b39d2 | 1405 | not_found.push(expected_error); |
a7813a04 XL |
1406 | } |
1407 | } | |
1408 | ||
041b39d2 | 1409 | if !unexpected.is_empty() || !not_found.is_empty() { |
ff7c6d11 XL |
1410 | self.error(&format!( |
1411 | "{} unexpected errors found, {} expected errors not found", | |
1412 | unexpected.len(), | |
1413 | not_found.len() | |
1414 | )); | |
1415 | println!("status: {}\ncommand: {}", proc_res.status, proc_res.cmdline); | |
041b39d2 | 1416 | if !unexpected.is_empty() { |
3157f602 XL |
1417 | println!("unexpected errors (from JSON output): {:#?}\n", unexpected); |
1418 | } | |
041b39d2 | 1419 | if !not_found.is_empty() { |
3157f602 XL |
1420 | println!("not found errors (from test file): {:#?}\n", not_found); |
1421 | } | |
a7813a04 XL |
1422 | panic!(); |
1423 | } | |
1424 | } | |
1425 | ||
9fa01778 | 1426 | /// Returns `true` if we should report an error about `actual_error`, |
a7813a04 XL |
1427 | /// which did not match any of the expected error. We always require |
1428 | /// errors/warnings to be explicitly listed, but only require | |
1429 | /// helps/notes if there are explicit helps/notes given. | |
ff7c6d11 XL |
1430 | fn is_unexpected_compiler_message( |
1431 | &self, | |
1432 | actual_error: &Error, | |
1433 | expect_help: bool, | |
1434 | expect_note: bool, | |
1435 | ) -> bool { | |
a7813a04 XL |
1436 | match actual_error.kind { |
1437 | Some(ErrorKind::Help) => expect_help, | |
1438 | Some(ErrorKind::Note) => expect_note, | |
ff7c6d11 XL |
1439 | Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true, |
1440 | Some(ErrorKind::Suggestion) | None => false, | |
a7813a04 XL |
1441 | } |
1442 | } | |
1443 | ||
487cf647 | 1444 | fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit { |
dfeec247 | 1445 | match (pm, self.props.fail_mode, self.config.mode) { |
487cf647 FG |
1446 | (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), Ui) => Emit::Metadata, |
1447 | _ => Emit::None, | |
dfeec247 XL |
1448 | } |
1449 | } | |
1450 | ||
487cf647 FG |
1451 | fn compile_test(&self, will_execute: WillExecute, emit: Emit) -> ProcRes { |
1452 | self.compile_test_general(will_execute, emit, self.props.local_pass_mode()) | |
dfeec247 XL |
1453 | } |
1454 | ||
1455 | fn compile_test_general( | |
1456 | &self, | |
1457 | will_execute: WillExecute, | |
487cf647 | 1458 | emit: Emit, |
dfeec247 XL |
1459 | local_pm: Option<PassMode>, |
1460 | ) -> ProcRes { | |
48663c56 | 1461 | // Only use `make_exe_name` when the test ends up being executed. |
dfeec247 XL |
1462 | let output_file = match will_execute { |
1463 | WillExecute::Yes => TargetLocation::ThisFile(self.make_exe_name()), | |
17df50a5 XL |
1464 | WillExecute::No | WillExecute::Disabled => { |
1465 | TargetLocation::ThisDirectory(self.output_base_dir()) | |
1466 | } | |
48663c56 XL |
1467 | }; |
1468 | ||
74b04a01 | 1469 | let allow_unused = match self.config.mode { |
5869c6ff XL |
1470 | Ui => { |
1471 | // UI tests tend to have tons of unused code as | |
3b2f2976 XL |
1472 | // it's just testing various pieces of the compile, but we don't |
1473 | // want to actually assert warnings about all this code. Instead | |
1474 | // let's just ignore unused code warnings by defaults and tests | |
1475 | // can turn it back on if needed. | |
dfeec247 XL |
1476 | if !self.is_rustdoc() |
1477 | // Note that we use the local pass mode here as we don't want | |
74b04a01 | 1478 | // to set unused to allow if we've overridden the pass mode |
e1599b0c | 1479 | // via command line flags. |
dfeec247 XL |
1480 | && local_pm != Some(PassMode::Run) |
1481 | { | |
74b04a01 XL |
1482 | AllowUnused::Yes |
1483 | } else { | |
1484 | AllowUnused::No | |
83c7162d | 1485 | } |
3b2f2976 | 1486 | } |
74b04a01 XL |
1487 | _ => AllowUnused::No, |
1488 | }; | |
1489 | ||
487cf647 FG |
1490 | let rustc = self.make_compile_args( |
1491 | &self.testpaths.file, | |
1492 | output_file, | |
1493 | emit, | |
1494 | allow_unused, | |
1495 | LinkToAux::Yes, | |
1496 | ); | |
3b2f2976 XL |
1497 | |
1498 | self.compose_and_run_compiler(rustc, None) | |
a7813a04 XL |
1499 | } |
1500 | ||
1501 | fn document(&self, out_dir: &Path) -> ProcRes { | |
1502 | if self.props.build_aux_docs { | |
1503 | for rel_ab in &self.props.aux_builds { | |
1504 | let aux_testpaths = self.compute_aux_test_paths(rel_ab); | |
ff7c6d11 | 1505 | let aux_props = |
dfeec247 | 1506 | self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config); |
a7813a04 XL |
1507 | let aux_cx = TestCx { |
1508 | config: self.config, | |
1509 | props: &aux_props, | |
1510 | testpaths: &aux_testpaths, | |
ff7c6d11 | 1511 | revision: self.revision, |
a7813a04 | 1512 | }; |
94b46f34 XL |
1513 | // Create the directory for the stdout/stderr files. |
1514 | create_dir_all(aux_cx.output_base_dir()).unwrap(); | |
a7813a04 XL |
1515 | let auxres = aux_cx.document(out_dir); |
1516 | if !auxres.status.success() { | |
1517 | return auxres; | |
1518 | } | |
1519 | } | |
1520 | } | |
1521 | ||
1522 | let aux_dir = self.aux_output_dir_name(); | |
3b2f2976 | 1523 | |
6a06907d | 1524 | let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path not passed"); |
3b2f2976 XL |
1525 | let mut rustdoc = Command::new(rustdoc_path); |
1526 | ||
ff7c6d11 | 1527 | rustdoc |
0531ce1d XL |
1528 | .arg("-L") |
1529 | .arg(self.config.run_lib_path.to_str().unwrap()) | |
ff7c6d11 XL |
1530 | .arg("-L") |
1531 | .arg(aux_dir) | |
1532 | .arg("-o") | |
1533 | .arg(out_dir) | |
a2a8927a XL |
1534 | .arg("--deny") |
1535 | .arg("warnings") | |
3b2f2976 XL |
1536 | .arg(&self.testpaths.file) |
1537 | .args(&self.props.compile_flags); | |
8faf50e0 | 1538 | |
fc512014 | 1539 | if self.config.mode == RustdocJson { |
cdc7bbd5 | 1540 | rustdoc.arg("--output-format").arg("json").arg("-Zunstable-options"); |
fc512014 XL |
1541 | } |
1542 | ||
abe05a73 | 1543 | if let Some(ref linker) = self.config.linker { |
e1599b0c | 1544 | rustdoc.arg(format!("-Clinker={}", linker)); |
abe05a73 | 1545 | } |
3b2f2976 XL |
1546 | |
1547 | self.compose_and_run_compiler(rustdoc, None) | |
a7813a04 XL |
1548 | } |
1549 | ||
1550 | fn exec_compiled_test(&self) -> ProcRes { | |
3b2f2976 | 1551 | let env = &self.props.exec_env; |
a7813a04 | 1552 | |
ff7c6d11 | 1553 | let proc_res = match &*self.config.target { |
8bb4bdeb XL |
1554 | // This is pretty similar to below, we're transforming: |
1555 | // | |
1556 | // program arg1 arg2 | |
1557 | // | |
1558 | // into | |
1559 | // | |
f9f354fc | 1560 | // remote-test-client run program 2 support-lib.so support-lib2.so arg1 arg2 |
8bb4bdeb XL |
1561 | // |
1562 | // The test-client program will upload `program` to the emulator | |
1563 | // along with all other support libraries listed (in this case | |
f9f354fc XL |
1564 | // `support-lib.so` and `support-lib2.so`. It will then execute |
1565 | // the program on the emulator with the arguments specified | |
1566 | // (in the environment we give the process) and then report back | |
1567 | // the same result. | |
7cac9316 | 1568 | _ if self.config.remote_test_client.is_some() => { |
8bb4bdeb | 1569 | let aux_dir = self.aux_output_dir_name(); |
f9f354fc XL |
1570 | let ProcArgs { prog, args } = self.make_run_args(); |
1571 | let mut support_libs = Vec::new(); | |
8bb4bdeb XL |
1572 | if let Ok(entries) = aux_dir.read_dir() { |
1573 | for entry in entries { | |
1574 | let entry = entry.unwrap(); | |
1575 | if !entry.path().is_file() { | |
ff7c6d11 | 1576 | continue; |
8bb4bdeb | 1577 | } |
f9f354fc | 1578 | support_libs.push(entry.path()); |
8bb4bdeb XL |
1579 | } |
1580 | } | |
ff7c6d11 XL |
1581 | let mut test_client = |
1582 | Command::new(self.config.remote_test_client.as_ref().unwrap()); | |
f9f354fc XL |
1583 | test_client |
1584 | .args(&["run", &support_libs.len().to_string(), &prog]) | |
1585 | .args(support_libs) | |
1586 | .args(args) | |
1587 | .envs(env.clone()); | |
ff7c6d11 XL |
1588 | self.compose_and_run( |
1589 | test_client, | |
1590 | self.config.run_lib_path.to_str().unwrap(), | |
1591 | Some(aux_dir.to_str().unwrap()), | |
1592 | None, | |
1593 | ) | |
8bb4bdeb | 1594 | } |
416331ca XL |
1595 | _ if self.config.target.contains("vxworks") => { |
1596 | let aux_dir = self.aux_output_dir_name(); | |
1597 | let ProcArgs { prog, args } = self.make_run_args(); | |
e1599b0c XL |
1598 | let mut wr_run = Command::new("wr-run"); |
1599 | wr_run.args(&[&prog]).args(args).envs(env.clone()); | |
416331ca | 1600 | self.compose_and_run( |
e1599b0c | 1601 | wr_run, |
416331ca XL |
1602 | self.config.run_lib_path.to_str().unwrap(), |
1603 | Some(aux_dir.to_str().unwrap()), | |
1604 | None, | |
1605 | ) | |
1606 | } | |
8bb4bdeb | 1607 | _ => { |
a7813a04 | 1608 | let aux_dir = self.aux_output_dir_name(); |
3b2f2976 XL |
1609 | let ProcArgs { prog, args } = self.make_run_args(); |
1610 | let mut program = Command::new(&prog); | |
dfeec247 | 1611 | program.args(args).current_dir(&self.output_base_dir()).envs(env.clone()); |
ff7c6d11 XL |
1612 | self.compose_and_run( |
1613 | program, | |
1614 | self.config.run_lib_path.to_str().unwrap(), | |
1615 | Some(aux_dir.to_str().unwrap()), | |
1616 | None, | |
1617 | ) | |
a7813a04 | 1618 | } |
ff7c6d11 XL |
1619 | }; |
1620 | ||
1621 | if proc_res.status.success() { | |
1622 | // delete the executable after running it to save space. | |
1623 | // it is ok if the deletion failed. | |
1624 | let _ = fs::remove_file(self.make_exe_name()); | |
a7813a04 | 1625 | } |
ff7c6d11 XL |
1626 | |
1627 | proc_res | |
a7813a04 XL |
1628 | } |
1629 | ||
1630 | /// For each `aux-build: foo/bar` annotation, we check to find the | |
94222f64 | 1631 | /// file in an `auxiliary` directory relative to the test itself. |
a7813a04 | 1632 | fn compute_aux_test_paths(&self, rel_ab: &str) -> TestPaths { |
94b46f34 XL |
1633 | let test_ab = self |
1634 | .testpaths | |
ff7c6d11 XL |
1635 | .file |
1636 | .parent() | |
1637 | .expect("test file path has no parent") | |
1638 | .join("auxiliary") | |
1639 | .join(rel_ab); | |
a7813a04 | 1640 | if !test_ab.exists() { |
dfeec247 | 1641 | self.fatal(&format!("aux-build `{}` source not found", test_ab.display())) |
a7813a04 XL |
1642 | } |
1643 | ||
1644 | TestPaths { | |
1645 | file: test_ab, | |
94b46f34 XL |
1646 | relative_dir: self |
1647 | .testpaths | |
ff7c6d11 | 1648 | .relative_dir |
94b46f34 | 1649 | .join(self.output_testname_unique()) |
ff7c6d11 XL |
1650 | .join("auxiliary") |
1651 | .join(rel_ab) | |
1652 | .parent() | |
1653 | .expect("aux-build path has no parent") | |
1654 | .to_path_buf(), | |
a7813a04 XL |
1655 | } |
1656 | } | |
1657 | ||
e1599b0c XL |
1658 | fn is_vxworks_pure_static(&self) -> bool { |
1659 | if self.config.target.contains("vxworks") { | |
1660 | match env::var("RUST_VXWORKS_TEST_DYLINK") { | |
1661 | Ok(s) => s != "1", | |
dfeec247 | 1662 | _ => true, |
e1599b0c XL |
1663 | } |
1664 | } else { | |
1665 | false | |
1666 | } | |
1667 | } | |
1668 | ||
1669 | fn is_vxworks_pure_dynamic(&self) -> bool { | |
1670 | self.config.target.contains("vxworks") && !self.is_vxworks_pure_static() | |
1671 | } | |
1672 | ||
fc512014 | 1673 | fn build_all_auxiliary(&self, rustc: &mut Command) -> PathBuf { |
94b46f34 XL |
1674 | let aux_dir = self.aux_output_dir_name(); |
1675 | ||
a7813a04 | 1676 | if !self.props.aux_builds.is_empty() { |
94b46f34 XL |
1677 | let _ = fs::remove_dir_all(&aux_dir); |
1678 | create_dir_all(&aux_dir).unwrap(); | |
a7813a04 XL |
1679 | } |
1680 | ||
a7813a04 | 1681 | for rel_ab in &self.props.aux_builds { |
60c5eb7d | 1682 | self.build_auxiliary(rel_ab, &aux_dir); |
a7813a04 XL |
1683 | } |
1684 | ||
60c5eb7d XL |
1685 | for (aux_name, aux_path) in &self.props.aux_crates { |
1686 | let is_dylib = self.build_auxiliary(&aux_path, &aux_dir); | |
dfeec247 XL |
1687 | let lib_name = |
1688 | get_lib_name(&aux_path.trim_end_matches(".rs").replace('-', "_"), is_dylib); | |
1689 | rustc.arg("--extern").arg(format!("{}={}/{}", aux_name, aux_dir.display(), lib_name)); | |
48663c56 XL |
1690 | } |
1691 | ||
fc512014 XL |
1692 | aux_dir |
1693 | } | |
1694 | ||
1695 | fn compose_and_run_compiler(&self, mut rustc: Command, input: Option<String>) -> ProcRes { | |
1696 | let aux_dir = self.build_all_auxiliary(&mut rustc); | |
f2b60f7d | 1697 | self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove); |
3b2f2976 | 1698 | rustc.envs(self.props.rustc_env.clone()); |
ff7c6d11 XL |
1699 | self.compose_and_run( |
1700 | rustc, | |
1701 | self.config.compile_lib_path.to_str().unwrap(), | |
1702 | Some(aux_dir.to_str().unwrap()), | |
1703 | input, | |
1704 | ) | |
1705 | } | |
1706 | ||
60c5eb7d XL |
1707 | /// Builds an aux dependency. |
1708 | /// | |
1709 | /// Returns whether or not it is a dylib. | |
1710 | fn build_auxiliary(&self, source_path: &str, aux_dir: &Path) -> bool { | |
1711 | let aux_testpaths = self.compute_aux_test_paths(source_path); | |
5099ac24 | 1712 | let aux_props = self.props.from_aux_file(&aux_testpaths.file, self.revision, self.config); |
60c5eb7d XL |
1713 | let aux_output = TargetLocation::ThisDirectory(self.aux_output_dir_name()); |
1714 | let aux_cx = TestCx { | |
1715 | config: self.config, | |
1716 | props: &aux_props, | |
1717 | testpaths: &aux_testpaths, | |
1718 | revision: self.revision, | |
1719 | }; | |
1720 | // Create the directory for the stdout/stderr files. | |
1721 | create_dir_all(aux_cx.output_base_dir()).unwrap(); | |
dfeec247 | 1722 | let input_file = &aux_testpaths.file; |
487cf647 FG |
1723 | let mut aux_rustc = aux_cx.make_compile_args( |
1724 | input_file, | |
1725 | aux_output, | |
1726 | Emit::None, | |
1727 | AllowUnused::No, | |
1728 | LinkToAux::No, | |
1729 | ); | |
60c5eb7d | 1730 | |
29967ef6 XL |
1731 | for key in &aux_props.unset_rustc_env { |
1732 | aux_rustc.env_remove(key); | |
1733 | } | |
1734 | aux_rustc.envs(aux_props.rustc_env.clone()); | |
1735 | ||
60c5eb7d XL |
1736 | let (dylib, crate_type) = if aux_props.no_prefer_dynamic { |
1737 | (true, None) | |
fc512014 | 1738 | } else if self.config.target.contains("emscripten") |
60c5eb7d XL |
1739 | || (self.config.target.contains("musl") |
1740 | && !aux_props.force_host | |
1741 | && !self.config.host.contains("musl")) | |
1742 | || self.config.target.contains("wasm32") | |
1743 | || self.config.target.contains("nvptx") | |
1744 | || self.is_vxworks_pure_static() | |
f9f354fc | 1745 | || self.config.target.contains("sgx") |
17df50a5 | 1746 | || self.config.target.contains("bpf") |
60c5eb7d XL |
1747 | { |
1748 | // We primarily compile all auxiliary libraries as dynamic libraries | |
1749 | // to avoid code size bloat and large binaries as much as possible | |
1750 | // for the test suite (otherwise including libstd statically in all | |
1751 | // executables takes up quite a bit of space). | |
1752 | // | |
1753 | // For targets like MUSL or Emscripten, however, there is no support for | |
1754 | // dynamic libraries so we just go back to building a normal library. Note, | |
1755 | // however, that for MUSL if the library is built with `force_host` then | |
1756 | // it's ok to be a dylib as the host should always support dylibs. | |
1757 | (false, Some("lib")) | |
1758 | } else { | |
1759 | (true, Some("dylib")) | |
1760 | }; | |
1761 | ||
1762 | if let Some(crate_type) = crate_type { | |
1763 | aux_rustc.args(&["--crate-type", crate_type]); | |
1764 | } | |
1765 | ||
1766 | aux_rustc.arg("-L").arg(&aux_dir); | |
1767 | ||
1768 | let auxres = aux_cx.compose_and_run( | |
1769 | aux_rustc, | |
1770 | aux_cx.config.compile_lib_path.to_str().unwrap(), | |
1771 | Some(aux_dir.to_str().unwrap()), | |
1772 | None, | |
1773 | ); | |
1774 | if !auxres.status.success() { | |
1775 | self.fatal_proc_rec( | |
1776 | &format!( | |
1777 | "auxiliary build of {:?} failed to compile: ", | |
1778 | aux_testpaths.file.display() | |
1779 | ), | |
1780 | &auxres, | |
1781 | ); | |
1782 | } | |
1783 | dylib | |
1784 | } | |
1785 | ||
923072b8 FG |
1786 | fn read2_abbreviated(&self, child: Child) -> Output { |
1787 | let mut filter_paths_from_len = Vec::new(); | |
1788 | let mut add_path = |path: &Path| { | |
1789 | let path = path.display().to_string(); | |
1790 | let windows = path.replace("\\", "\\\\"); | |
1791 | if windows != path { | |
1792 | filter_paths_from_len.push(windows); | |
1793 | } | |
1794 | filter_paths_from_len.push(path); | |
1795 | }; | |
1796 | ||
1797 | // List of paths that will not be measured when determining whether the output is larger | |
1798 | // than the output truncation threshold. | |
1799 | // | |
1800 | // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the | |
1801 | // same slice of text will be double counted and the truncation might not happen. | |
1802 | add_path(&self.config.src_base); | |
1803 | add_path(&self.config.build_base); | |
1804 | ||
1805 | read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output") | |
1806 | } | |
1807 | ||
ff7c6d11 XL |
1808 | fn compose_and_run( |
1809 | &self, | |
1810 | mut command: Command, | |
1811 | lib_path: &str, | |
1812 | aux_path: Option<&str>, | |
1813 | input: Option<String>, | |
1814 | ) -> ProcRes { | |
1815 | let cmdline = { | |
3b2f2976 XL |
1816 | let cmdline = self.make_cmdline(&command, lib_path); |
1817 | logv(self.config, format!("executing {}", cmdline)); | |
1818 | cmdline | |
1819 | }; | |
a7813a04 | 1820 | |
dfeec247 | 1821 | command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped()); |
3b2f2976 XL |
1822 | |
1823 | // Need to be sure to put both the lib_path and the aux path in the dylib | |
1824 | // search path for the child. | |
487cf647 | 1825 | add_dylib_path(&mut command, iter::once(lib_path).chain(aux_path)); |
3b2f2976 | 1826 | |
83c7162d | 1827 | let mut child = disable_error_reporting(|| command.spawn()) |
3dfed10e | 1828 | .unwrap_or_else(|_| panic!("failed to exec `{:?}`", &command)); |
3b2f2976 | 1829 | if let Some(input) = input { |
dfeec247 | 1830 | child.stdin.as_mut().unwrap().write_all(input.as_bytes()).unwrap(); |
3b2f2976 | 1831 | } |
abe05a73 | 1832 | |
923072b8 | 1833 | let Output { status, stdout, stderr } = self.read2_abbreviated(child); |
3b2f2976 XL |
1834 | |
1835 | let result = ProcRes { | |
1836 | status, | |
abe05a73 XL |
1837 | stdout: String::from_utf8_lossy(&stdout).into_owned(), |
1838 | stderr: String::from_utf8_lossy(&stderr).into_owned(), | |
3b2f2976 | 1839 | cmdline, |
a7813a04 XL |
1840 | }; |
1841 | ||
3b2f2976 XL |
1842 | self.dump_output(&result.stdout, &result.stderr); |
1843 | ||
1844 | result | |
1845 | } | |
1846 | ||
dfeec247 | 1847 | fn is_rustdoc(&self) -> bool { |
fc512014 XL |
1848 | self.config.src_base.ends_with("rustdoc-ui") |
1849 | || self.config.src_base.ends_with("rustdoc-js") | |
1850 | || self.config.src_base.ends_with("rustdoc-json") | |
dfeec247 XL |
1851 | } |
1852 | ||
dc9dc135 XL |
1853 | fn make_compile_args( |
1854 | &self, | |
1855 | input_file: &Path, | |
1856 | output_file: TargetLocation, | |
487cf647 | 1857 | emit: Emit, |
74b04a01 | 1858 | allow_unused: AllowUnused, |
487cf647 | 1859 | link_to_aux: LinkToAux, |
dc9dc135 | 1860 | ) -> Command { |
3dfed10e XL |
1861 | let is_aux = input_file.components().map(|c| c.as_os_str()).any(|c| c == "auxiliary"); |
1862 | let is_rustdoc = self.is_rustdoc() && !is_aux; | |
83c7162d XL |
1863 | let mut rustc = if !is_rustdoc { |
1864 | Command::new(&self.config.rustc_path) | |
1865 | } else { | |
dfeec247 | 1866 | Command::new(&self.config.rustdoc_path.clone().expect("no rustdoc built yet")) |
83c7162d | 1867 | }; |
cdc7bbd5 | 1868 | rustc.arg(input_file); |
476ff2be | 1869 | |
9fa01778 XL |
1870 | // Use a single thread for efficiency and a deterministic error message order |
1871 | rustc.arg("-Zthreads=1"); | |
1872 | ||
476ff2be | 1873 | // Optionally prevent default --target if specified in test compile-flags. |
dfeec247 | 1874 | let custom_target = self.props.compile_flags.iter().any(|x| x.starts_with("--target")); |
476ff2be SL |
1875 | |
1876 | if !custom_target { | |
dfeec247 XL |
1877 | let target = |
1878 | if self.props.force_host { &*self.config.host } else { &*self.config.target }; | |
3b2f2976 XL |
1879 | |
1880 | rustc.arg(&format!("--target={}", target)); | |
476ff2be | 1881 | } |
9fa01778 | 1882 | self.set_revision_flags(&mut rustc); |
a7813a04 | 1883 | |
83c7162d XL |
1884 | if !is_rustdoc { |
1885 | if let Some(ref incremental_dir) = self.props.incremental_dir { | |
94b46f34 | 1886 | rustc.args(&["-C", &format!("incremental={}", incremental_dir.display())]); |
83c7162d | 1887 | rustc.args(&["-Z", "incremental-verify-ich"]); |
83c7162d | 1888 | } |
a7813a04 | 1889 | |
83c7162d XL |
1890 | if self.config.mode == CodegenUnits { |
1891 | rustc.args(&["-Z", "human_readable_cgu_names"]); | |
1892 | } | |
ff7c6d11 XL |
1893 | } |
1894 | ||
064997fb FG |
1895 | if self.config.optimize_tests && !is_rustdoc { |
1896 | match self.config.mode { | |
1897 | Ui => { | |
1898 | // If optimize-tests is true we still only want to optimize tests that actually get | |
1899 | // executed and that don't specify their own optimization levels. | |
1900 | // Note: aux libs don't have a pass-mode, so they won't get optimized | |
1901 | // unless compile-flags are set in the aux file. | |
1902 | if self.config.optimize_tests | |
1903 | && self.props.pass_mode(&self.config) == Some(PassMode::Run) | |
1904 | && !self | |
1905 | .props | |
1906 | .compile_flags | |
1907 | .iter() | |
1908 | .any(|arg| arg == "-O" || arg.contains("opt-level")) | |
1909 | { | |
1910 | rustc.arg("-O"); | |
1911 | } | |
1912 | } | |
1913 | DebugInfo => { /* debuginfo tests must be unoptimized */ } | |
1914 | _ => { | |
1915 | rustc.arg("-O"); | |
1916 | } | |
1917 | } | |
1918 | } | |
1919 | ||
a7813a04 | 1920 | match self.config.mode { |
5869c6ff | 1921 | Incremental => { |
a7813a04 XL |
1922 | // If we are extracting and matching errors in the new |
1923 | // fashion, then you want JSON mode. Old-skool error | |
1924 | // patterns still match the raw compiler output. | |
064997fb FG |
1925 | if self.props.error_patterns.is_empty() |
1926 | && self.props.regex_error_patterns.is_empty() | |
1927 | { | |
3b2f2976 | 1928 | rustc.args(&["--error-format", "json"]); |
a2a8927a | 1929 | rustc.args(&["--json", "future-incompat"]); |
a7813a04 | 1930 | } |
dfeec247 XL |
1931 | rustc.arg("-Zui-testing"); |
1932 | rustc.arg("-Zdeduplicate-diagnostics=no"); | |
0531ce1d | 1933 | } |
416331ca | 1934 | Ui => { |
dfeec247 | 1935 | if !self.props.compile_flags.iter().any(|s| s.starts_with("--error-format")) { |
0531ce1d | 1936 | rustc.args(&["--error-format", "json"]); |
a2a8927a | 1937 | rustc.args(&["--json", "future-incompat"]); |
0531ce1d | 1938 | } |
cdc7bbd5 | 1939 | rustc.arg("-Ccodegen-units=1"); |
9c376795 | 1940 | // Hide line numbers to reduce churn |
dfeec247 | 1941 | rustc.arg("-Zui-testing"); |
9c376795 FG |
1942 | // Hide libstd sources from ui tests to make sure we generate the stderr |
1943 | // output that users will see. | |
1944 | // Without this, we may be producing good diagnostics in-tree but users | |
1945 | // will not see half the information. | |
1946 | rustc.arg("-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX"); | |
1947 | rustc.arg("-Ztranslate-remapped-path-to-local-path=no"); | |
1948 | ||
dfeec247 | 1949 | rustc.arg("-Zdeduplicate-diagnostics=no"); |
064997fb FG |
1950 | // FIXME: use this for other modes too, for perf? |
1951 | rustc.arg("-Cstrip=debuginfo"); | |
a7813a04 | 1952 | } |
5bcae85e | 1953 | MirOpt => { |
3b2f2976 | 1954 | rustc.args(&[ |
6a06907d | 1955 | "-Copt-level=1", |
3b2f2976 | 1956 | "-Zdump-mir=all", |
3dfed10e | 1957 | "-Zvalidate-mir", |
ff7c6d11 | 1958 | "-Zdump-mir-exclude-pass-number", |
064997fb | 1959 | "-Zmir-pretty-relative-line-numbers=yes", |
ff7c6d11 | 1960 | ]); |
04454e1e FG |
1961 | if let Some(pass) = &self.props.mir_unit_test { |
1962 | rustc.args(&["-Zmir-opt-level=0", &format!("-Zmir-enable-passes=+{}", pass)]); | |
1963 | } else { | |
1964 | rustc.arg("-Zmir-opt-level=4"); | |
1965 | } | |
5bcae85e SL |
1966 | |
1967 | let mir_dump_dir = self.get_mir_dump_dir(); | |
abe05a73 | 1968 | let _ = fs::remove_dir_all(&mir_dump_dir); |
cc61c64b | 1969 | create_dir_all(mir_dump_dir.as_path()).unwrap(); |
7cac9316 | 1970 | let mut dir_opt = "-Zdump-mir-dir=".to_string(); |
5bcae85e SL |
1971 | dir_opt.push_str(mir_dump_dir.to_str().unwrap()); |
1972 | debug!("dir_opt: {:?}", dir_opt); | |
1973 | ||
3b2f2976 | 1974 | rustc.arg(dir_opt); |
5bcae85e | 1975 | } |
5869c6ff XL |
1976 | RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson | RunMake |
1977 | | CodegenUnits | JsDocTest | Assembly => { | |
a7813a04 XL |
1978 | // do not use JSON output |
1979 | } | |
1980 | } | |
1981 | ||
9c376795 FG |
1982 | if self.props.remap_src_base { |
1983 | rustc.arg(format!( | |
1984 | "--remap-path-prefix={}={}", | |
1985 | self.config.src_base.display(), | |
1986 | FAKE_SRC_BASE, | |
1987 | )); | |
1988 | } | |
1989 | ||
487cf647 FG |
1990 | match emit { |
1991 | Emit::None => {} | |
1992 | Emit::Metadata if is_rustdoc => {} | |
1993 | Emit::Metadata => { | |
1994 | rustc.args(&["--emit", "metadata"]); | |
1995 | } | |
1996 | Emit::LlvmIr => { | |
1997 | rustc.args(&["--emit", "llvm-ir"]); | |
1998 | } | |
1999 | Emit::Asm => { | |
2000 | rustc.args(&["--emit", "asm"]); | |
2001 | } | |
83c7162d | 2002 | } |
abe05a73 | 2003 | |
83c7162d | 2004 | if !is_rustdoc { |
dfeec247 | 2005 | if self.config.target == "wasm32-unknown-unknown" || self.is_vxworks_pure_static() { |
83c7162d XL |
2006 | // rustc.arg("-g"); // get any backtrace at all on errors |
2007 | } else if !self.props.no_prefer_dynamic { | |
2008 | rustc.args(&["-C", "prefer-dynamic"]); | |
2009 | } | |
a7813a04 | 2010 | } |
3b2f2976 XL |
2011 | |
2012 | match output_file { | |
a7813a04 | 2013 | TargetLocation::ThisFile(path) => { |
3b2f2976 | 2014 | rustc.arg("-o").arg(path); |
a7813a04 XL |
2015 | } |
2016 | TargetLocation::ThisDirectory(path) => { | |
48663c56 XL |
2017 | if is_rustdoc { |
2018 | // `rustdoc` uses `-o` for the output directory. | |
2019 | rustc.arg("-o").arg(path); | |
2020 | } else { | |
2021 | rustc.arg("--out-dir").arg(path); | |
2022 | } | |
a7813a04 | 2023 | } |
3b2f2976 XL |
2024 | } |
2025 | ||
83c7162d | 2026 | match self.config.compare_mode { |
94b46f34 | 2027 | Some(CompareMode::Polonius) => { |
923072b8 | 2028 | rustc.args(&["-Zpolonius"]); |
94b46f34 | 2029 | } |
f035d41b | 2030 | Some(CompareMode::Chalk) => { |
9c376795 | 2031 | rustc.args(&["-Ztrait-solver=chalk"]); |
f035d41b | 2032 | } |
f2b60f7d | 2033 | Some(CompareMode::SplitDwarf) if self.config.target.contains("windows") => { |
5869c6ff | 2034 | rustc.args(&["-Csplit-debuginfo=unpacked", "-Zunstable-options"]); |
fc512014 | 2035 | } |
f2b60f7d FG |
2036 | Some(CompareMode::SplitDwarf) => { |
2037 | rustc.args(&["-Csplit-debuginfo=unpacked"]); | |
2038 | } | |
fc512014 | 2039 | Some(CompareMode::SplitDwarfSingle) => { |
f2b60f7d | 2040 | rustc.args(&["-Csplit-debuginfo=packed"]); |
fc512014 | 2041 | } |
94b46f34 | 2042 | None => {} |
83c7162d XL |
2043 | } |
2044 | ||
ba9703b0 XL |
2045 | // Add `-A unused` before `config` flags and in-test (`props`) flags, so that they can |
2046 | // overwrite this. | |
2047 | if let AllowUnused::Yes = allow_unused { | |
2048 | rustc.args(&["-A", "unused"]); | |
2049 | } | |
2050 | ||
a7813a04 | 2051 | if self.props.force_host { |
2b03887a | 2052 | self.maybe_add_external_args(&mut rustc, &self.config.host_rustcflags); |
a7813a04 | 2053 | } else { |
2b03887a | 2054 | self.maybe_add_external_args(&mut rustc, &self.config.target_rustcflags); |
a1dfa0c6 XL |
2055 | if !is_rustdoc { |
2056 | if let Some(ref linker) = self.config.linker { | |
2057 | rustc.arg(format!("-Clinker={}", linker)); | |
2058 | } | |
83c7162d | 2059 | } |
abe05a73 | 2060 | } |
3b2f2976 | 2061 | |
48663c56 | 2062 | // Use dynamic musl for tests because static doesn't allow creating dylibs |
dfeec247 | 2063 | if self.config.host.contains("musl") || self.is_vxworks_pure_dynamic() { |
48663c56 XL |
2064 | rustc.arg("-Ctarget-feature=-crt-static"); |
2065 | } | |
2066 | ||
487cf647 FG |
2067 | if let LinkToAux::Yes = link_to_aux { |
2068 | rustc.arg("-L").arg(self.aux_output_dir_name()); | |
2069 | } | |
2070 | ||
3b2f2976 XL |
2071 | rustc.args(&self.props.compile_flags); |
2072 | ||
2073 | rustc | |
a7813a04 XL |
2074 | } |
2075 | ||
a7813a04 | 2076 | fn make_exe_name(&self) -> PathBuf { |
94b46f34 XL |
2077 | // Using a single letter here to keep the path length down for |
2078 | // Windows. Some test names get very long. rustc creates `rcgu` | |
2079 | // files with the module name appended to it which can more than | |
2080 | // double the length. | |
2081 | let mut f = self.output_base_dir().join("a"); | |
a7813a04 | 2082 | // FIXME: This is using the host architecture exe suffix, not target! |
c30ab7b3 | 2083 | if self.config.target.contains("emscripten") { |
83c7162d | 2084 | f = f.with_extra_extension("js"); |
abe05a73 | 2085 | } else if self.config.target.contains("wasm32") { |
83c7162d | 2086 | f = f.with_extra_extension("wasm"); |
6a06907d XL |
2087 | } else if self.config.target.contains("spirv") { |
2088 | f = f.with_extra_extension("spv"); | |
a7813a04 | 2089 | } else if !env::consts::EXE_SUFFIX.is_empty() { |
83c7162d | 2090 | f = f.with_extra_extension(env::consts::EXE_SUFFIX); |
a7813a04 XL |
2091 | } |
2092 | f | |
2093 | } | |
2094 | ||
2095 | fn make_run_args(&self) -> ProcArgs { | |
2096 | // If we've got another tool to run under (valgrind), | |
2097 | // then split apart its command | |
2098 | let mut args = self.split_maybe_args(&self.config.runtool); | |
2099 | ||
2100 | // If this is emscripten, then run tests under nodejs | |
c30ab7b3 | 2101 | if self.config.target.contains("emscripten") { |
476ff2be SL |
2102 | if let Some(ref p) = self.config.nodejs { |
2103 | args.push(p.clone()); | |
2104 | } else { | |
2105 | self.fatal("no NodeJS binary found (--nodejs)"); | |
2106 | } | |
a1dfa0c6 | 2107 | // If this is otherwise wasm, then run tests under nodejs with our |
abe05a73 | 2108 | // shim |
a1dfa0c6 | 2109 | } else if self.config.target.contains("wasm32") { |
abe05a73 XL |
2110 | if let Some(ref p) = self.config.nodejs { |
2111 | args.push(p.clone()); | |
2112 | } else { | |
2113 | self.fatal("no NodeJS binary found (--nodejs)"); | |
2114 | } | |
2115 | ||
dfeec247 XL |
2116 | let src = self |
2117 | .config | |
2118 | .src_base | |
2119 | .parent() | |
2120 | .unwrap() // chop off `ui` | |
2121 | .parent() | |
9c376795 | 2122 | .unwrap(); // chop off `tests` |
abe05a73 XL |
2123 | args.push(src.join("src/etc/wasm32-shim.js").display().to_string()); |
2124 | } | |
2125 | ||
a7813a04 XL |
2126 | let exe_file = self.make_exe_name(); |
2127 | ||
2128 | // FIXME (#9639): This needs to handle non-utf8 paths | |
2129 | args.push(exe_file.to_str().unwrap().to_owned()); | |
2130 | ||
2131 | // Add the arguments in the run_flags directive | |
2132 | args.extend(self.split_maybe_args(&self.props.run_flags)); | |
2133 | ||
2134 | let prog = args.remove(0); | |
ff7c6d11 | 2135 | ProcArgs { prog, args } |
a7813a04 XL |
2136 | } |
2137 | ||
2138 | fn split_maybe_args(&self, argstr: &Option<String>) -> Vec<String> { | |
2139 | match *argstr { | |
94b46f34 XL |
2140 | Some(ref s) => s |
2141 | .split(' ') | |
ff7c6d11 | 2142 | .filter_map(|s| { |
dfeec247 | 2143 | if s.chars().all(|c| c.is_whitespace()) { None } else { Some(s.to_owned()) } |
ff7c6d11 XL |
2144 | }) |
2145 | .collect(), | |
2146 | None => Vec::new(), | |
a7813a04 XL |
2147 | } |
2148 | } | |
2149 | ||
3b2f2976 | 2150 | fn make_cmdline(&self, command: &Command, libpath: &str) -> String { |
9fa01778 | 2151 | use crate::util; |
a7813a04 XL |
2152 | |
2153 | // Linux and mac don't require adjusting the library search path | |
2154 | if cfg!(unix) { | |
3b2f2976 | 2155 | format!("{:?}", command) |
a7813a04 XL |
2156 | } else { |
2157 | // Build the LD_LIBRARY_PATH variable as it would be seen on the command line | |
2158 | // for diagnostic purposes | |
2159 | fn lib_path_cmd_prefix(path: &str) -> String { | |
dfeec247 | 2160 | format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path)) |
a7813a04 XL |
2161 | } |
2162 | ||
3b2f2976 | 2163 | format!("{} {:?}", lib_path_cmd_prefix(libpath), command) |
a7813a04 XL |
2164 | } |
2165 | } | |
2166 | ||
2167 | fn dump_output(&self, out: &str, err: &str) { | |
dfeec247 | 2168 | let revision = if let Some(r) = self.revision { format!("{}.", r) } else { String::new() }; |
cc61c64b XL |
2169 | |
2170 | self.dump_output_file(out, &format!("{}out", revision)); | |
2171 | self.dump_output_file(err, &format!("{}err", revision)); | |
a7813a04 XL |
2172 | self.maybe_dump_to_stdout(out, err); |
2173 | } | |
2174 | ||
ff7c6d11 | 2175 | fn dump_output_file(&self, out: &str, extension: &str) { |
a7813a04 | 2176 | let outfile = self.make_out_name(extension); |
0731742a | 2177 | fs::write(&outfile, out).unwrap(); |
a7813a04 XL |
2178 | } |
2179 | ||
9fa01778 XL |
2180 | /// Creates a filename for output with the given extension. |
2181 | /// E.g., `/.../testname.revision.mode/testname.extension`. | |
a7813a04 XL |
2182 | fn make_out_name(&self, extension: &str) -> PathBuf { |
2183 | self.output_base_name().with_extension(extension) | |
2184 | } | |
2185 | ||
9fa01778 XL |
2186 | /// Gets the directory where auxiliary files are written. |
2187 | /// E.g., `/.../testname.revision.mode/auxiliary/`. | |
a7813a04 | 2188 | fn aux_output_dir_name(&self) -> PathBuf { |
94b46f34 XL |
2189 | self.output_base_dir() |
2190 | .join("auxiliary") | |
83c7162d | 2191 | .with_extra_extension(self.config.mode.disambiguator()) |
a7813a04 XL |
2192 | } |
2193 | ||
94b46f34 XL |
2194 | /// Generates a unique name for the test, such as `testname.revision.mode`. |
2195 | fn output_testname_unique(&self) -> PathBuf { | |
2196 | output_testname_unique(self.config, self.testpaths, self.safe_revision()) | |
a7813a04 XL |
2197 | } |
2198 | ||
9fa01778 | 2199 | /// The revision, ignored for incremental compilation since it wants all revisions in |
94b46f34 XL |
2200 | /// the same directory. |
2201 | fn safe_revision(&self) -> Option<&str> { | |
dfeec247 | 2202 | if self.config.mode == Incremental { None } else { self.revision } |
94b46f34 | 2203 | } |
a7813a04 | 2204 | |
9fa01778 XL |
2205 | /// Gets the absolute path to the directory where all output for the given |
2206 | /// test/revision should reside. | |
2207 | /// E.g., `/path/to/build/host-triple/test/ui/relative/testname.revision.mode/`. | |
94b46f34 XL |
2208 | fn output_base_dir(&self) -> PathBuf { |
2209 | output_base_dir(self.config, self.testpaths, self.safe_revision()) | |
83c7162d XL |
2210 | } |
2211 | ||
9fa01778 XL |
2212 | /// Gets the absolute path to the base filename used as output for the given |
2213 | /// test/revision. | |
2214 | /// E.g., `/.../relative/testname.revision.mode/testname`. | |
94b46f34 XL |
2215 | fn output_base_name(&self) -> PathBuf { |
2216 | output_base_name(self.config, self.testpaths, self.safe_revision()) | |
a7813a04 XL |
2217 | } |
2218 | ||
2219 | fn maybe_dump_to_stdout(&self, out: &str, err: &str) { | |
2220 | if self.config.verbose { | |
c295e0f8 | 2221 | println!("------stdout------------------------------"); |
a7813a04 | 2222 | println!("{}", out); |
c295e0f8 | 2223 | println!("------stderr------------------------------"); |
a7813a04 XL |
2224 | println!("{}", err); |
2225 | println!("------------------------------------------"); | |
2226 | } | |
2227 | } | |
2228 | ||
2229 | fn error(&self, err: &str) { | |
2230 | match self.revision { | |
2231 | Some(rev) => println!("\nerror in revision `{}`: {}", rev, err), | |
ff7c6d11 | 2232 | None => println!("\nerror: {}", err), |
a7813a04 XL |
2233 | } |
2234 | } | |
2235 | ||
2236 | fn fatal(&self, err: &str) -> ! { | |
ff7c6d11 | 2237 | self.error(err); |
9fa01778 XL |
2238 | error!("fatal error, panic: {:?}", err); |
2239 | panic!("fatal error"); | |
a7813a04 XL |
2240 | } |
2241 | ||
2242 | fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! { | |
2243 | self.error(err); | |
fc512014 XL |
2244 | proc_res.fatal(None, || ()); |
2245 | } | |
2246 | ||
2247 | fn fatal_proc_rec_with_ctx( | |
2248 | &self, | |
2249 | err: &str, | |
2250 | proc_res: &ProcRes, | |
2251 | on_failure: impl FnOnce(Self), | |
2252 | ) -> ! { | |
2253 | self.error(err); | |
2254 | proc_res.fatal(None, || on_failure(*self)); | |
a7813a04 XL |
2255 | } |
2256 | ||
a7813a04 XL |
2257 | // codegen tests (using FileCheck) |
2258 | ||
2259 | fn compile_test_and_save_ir(&self) -> ProcRes { | |
94b46f34 | 2260 | let output_file = TargetLocation::ThisDirectory(self.output_base_dir()); |
dfeec247 | 2261 | let input_file = &self.testpaths.file; |
487cf647 FG |
2262 | let rustc = self.make_compile_args( |
2263 | input_file, | |
2264 | output_file, | |
2265 | Emit::LlvmIr, | |
2266 | AllowUnused::No, | |
2267 | LinkToAux::Yes, | |
2268 | ); | |
3b2f2976 XL |
2269 | |
2270 | self.compose_and_run_compiler(rustc, None) | |
a7813a04 XL |
2271 | } |
2272 | ||
532ac7d7 XL |
2273 | fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) { |
2274 | // This works with both `--emit asm` (as default output name for the assembly) | |
2275 | // and `ptx-linker` because the latter can write output at requested location. | |
2276 | let output_path = self.output_base_name().with_extension("s"); | |
2277 | ||
2278 | let output_file = TargetLocation::ThisFile(output_path.clone()); | |
dfeec247 | 2279 | let input_file = &self.testpaths.file; |
532ac7d7 | 2280 | |
487cf647 | 2281 | let mut emit = Emit::None; |
532ac7d7 XL |
2282 | match self.props.assembly_output.as_ref().map(AsRef::as_ref) { |
2283 | Some("emit-asm") => { | |
487cf647 | 2284 | emit = Emit::Asm; |
532ac7d7 XL |
2285 | } |
2286 | ||
2287 | Some("ptx-linker") => { | |
2288 | // No extra flags needed. | |
2289 | } | |
2290 | ||
2291 | Some(_) => self.fatal("unknown 'assembly-output' header"), | |
2292 | None => self.fatal("missing 'assembly-output' header"), | |
2293 | } | |
2294 | ||
487cf647 FG |
2295 | let rustc = |
2296 | self.make_compile_args(input_file, output_file, emit, AllowUnused::No, LinkToAux::Yes); | |
2297 | ||
532ac7d7 XL |
2298 | (self.compose_and_run_compiler(rustc, None), output_path) |
2299 | } | |
2300 | ||
2301 | fn verify_with_filecheck(&self, output: &Path) -> ProcRes { | |
3b2f2976 | 2302 | let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap()); |
dfeec247 | 2303 | filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file); |
9fa01778 XL |
2304 | // It would be more appropriate to make most of the arguments configurable through |
2305 | // a comment-attribute similar to `compile-flags`. For example, --check-prefixes is a very | |
2306 | // useful flag. | |
2307 | // | |
2308 | // For now, though… | |
136023e0 XL |
2309 | let prefix_for_target = |
2310 | if self.config.target.contains("msvc") { "MSVC" } else { "NONMSVC" }; | |
2311 | let prefixes = if let Some(rev) = self.revision { | |
2312 | format!("CHECK,{},{}", prefix_for_target, rev) | |
2313 | } else { | |
2314 | format!("CHECK,{}", prefix_for_target) | |
2315 | }; | |
2316 | if self.config.llvm_version.unwrap_or(0) >= 130000 { | |
2317 | filecheck.args(&["--allow-unused-prefixes", "--check-prefixes", &prefixes]); | |
2318 | } else { | |
2319 | filecheck.args(&["--check-prefixes", &prefixes]); | |
9fa01778 | 2320 | } |
9c376795 FG |
2321 | // Provide more context on failures. |
2322 | filecheck.args(&["--dump-input-context", "100"]); | |
3b2f2976 | 2323 | self.compose_and_run(filecheck, "", None, None) |
a7813a04 XL |
2324 | } |
2325 | ||
2326 | fn run_codegen_test(&self) { | |
a7813a04 XL |
2327 | if self.config.llvm_filecheck.is_none() { |
2328 | self.fatal("missing --llvm-filecheck"); | |
2329 | } | |
2330 | ||
532ac7d7 | 2331 | let proc_res = self.compile_test_and_save_ir(); |
a7813a04 XL |
2332 | if !proc_res.status.success() { |
2333 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
2334 | } | |
2335 | ||
532ac7d7 XL |
2336 | let output_path = self.output_base_name().with_extension("ll"); |
2337 | let proc_res = self.verify_with_filecheck(&output_path); | |
2338 | if !proc_res.status.success() { | |
2339 | self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res); | |
2340 | } | |
2341 | } | |
2342 | ||
2343 | fn run_assembly_test(&self) { | |
2344 | if self.config.llvm_filecheck.is_none() { | |
2345 | self.fatal("missing --llvm-filecheck"); | |
2346 | } | |
2347 | ||
2348 | let (proc_res, output_path) = self.compile_test_and_save_assembly(); | |
2349 | if !proc_res.status.success() { | |
2350 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
2351 | } | |
2352 | ||
2353 | let proc_res = self.verify_with_filecheck(&output_path); | |
a7813a04 XL |
2354 | if !proc_res.status.success() { |
2355 | self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res); | |
2356 | } | |
2357 | } | |
2358 | ||
2359 | fn charset() -> &'static str { | |
2360 | // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset | |
dfeec247 | 2361 | if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" } |
a7813a04 XL |
2362 | } |
2363 | ||
2364 | fn run_rustdoc_test(&self) { | |
2365 | assert!(self.revision.is_none(), "revisions not relevant here"); | |
2366 | ||
94b46f34 | 2367 | let out_dir = self.output_base_dir(); |
a7813a04 | 2368 | let _ = fs::remove_dir_all(&out_dir); |
cc61c64b | 2369 | create_dir_all(&out_dir).unwrap(); |
a7813a04 XL |
2370 | |
2371 | let proc_res = self.document(&out_dir); | |
2372 | if !proc_res.status.success() { | |
2373 | self.fatal_proc_rec("rustdoc failed!", &proc_res); | |
2374 | } | |
a7813a04 | 2375 | |
041b39d2 | 2376 | if self.props.check_test_line_numbers_match { |
8bb4bdeb XL |
2377 | self.check_rustdoc_test_option(proc_res); |
2378 | } else { | |
abe05a73 | 2379 | let root = self.config.find_rust_src_root().unwrap(); |
04454e1e | 2380 | let mut cmd = Command::new(&self.config.python); |
a2a8927a XL |
2381 | cmd.arg(root.join("src/etc/htmldocck.py")).arg(&out_dir).arg(&self.testpaths.file); |
2382 | if self.config.bless { | |
2383 | cmd.arg("--bless"); | |
2384 | } | |
2385 | let res = self.cmd2procres(&mut cmd); | |
8bb4bdeb | 2386 | if !res.status.success() { |
fc512014 XL |
2387 | self.fatal_proc_rec_with_ctx("htmldocck failed!", &res, |mut this| { |
2388 | this.compare_to_default_rustdoc(&out_dir) | |
2389 | }); | |
2390 | } | |
2391 | } | |
2392 | } | |
2393 | ||
2394 | fn compare_to_default_rustdoc(&mut self, out_dir: &Path) { | |
6a06907d XL |
2395 | if !self.config.has_tidy { |
2396 | return; | |
2397 | } | |
fc512014 XL |
2398 | println!("info: generating a diff against nightly rustdoc"); |
2399 | ||
2400 | let suffix = | |
2401 | self.safe_revision().map_or("nightly".into(), |path| path.to_owned() + "-nightly"); | |
2402 | let compare_dir = output_base_dir(self.config, self.testpaths, Some(&suffix)); | |
2403 | // Don't give an error if the directory didn't already exist | |
2404 | let _ = fs::remove_dir_all(&compare_dir); | |
2405 | create_dir_all(&compare_dir).unwrap(); | |
2406 | ||
2407 | // We need to create a new struct for the lifetimes on `config` to work. | |
2408 | let new_rustdoc = TestCx { | |
2409 | config: &Config { | |
2410 | // FIXME: use beta or a user-specified rustdoc instead of | |
2411 | // hardcoding the default toolchain | |
2412 | rustdoc_path: Some("rustdoc".into()), | |
2413 | // Needed for building auxiliary docs below | |
2414 | rustc_path: "rustc".into(), | |
2415 | ..self.config.clone() | |
2416 | }, | |
2417 | ..*self | |
2418 | }; | |
2419 | ||
2420 | let output_file = TargetLocation::ThisDirectory(new_rustdoc.aux_output_dir_name()); | |
2421 | let mut rustc = new_rustdoc.make_compile_args( | |
2422 | &new_rustdoc.testpaths.file, | |
2423 | output_file, | |
487cf647 | 2424 | Emit::None, |
fc512014 | 2425 | AllowUnused::Yes, |
487cf647 | 2426 | LinkToAux::Yes, |
fc512014 | 2427 | ); |
fc512014 XL |
2428 | new_rustdoc.build_all_auxiliary(&mut rustc); |
2429 | ||
2430 | let proc_res = new_rustdoc.document(&compare_dir); | |
2431 | if !proc_res.status.success() { | |
2432 | eprintln!("failed to run nightly rustdoc"); | |
2433 | return; | |
2434 | } | |
2435 | ||
2436 | #[rustfmt::skip] | |
2437 | let tidy_args = [ | |
2438 | "--indent", "yes", | |
2439 | "--indent-spaces", "2", | |
2440 | "--wrap", "0", | |
2441 | "--show-warnings", "no", | |
2442 | "--markup", "yes", | |
2443 | "--quiet", "yes", | |
2444 | "-modify", | |
2445 | ]; | |
2446 | let tidy_dir = |dir| { | |
2447 | for entry in walkdir::WalkDir::new(dir) { | |
2448 | let entry = entry.expect("failed to read file"); | |
2449 | if entry.file_type().is_file() | |
2450 | && entry.path().extension().and_then(|p| p.to_str()) == Some("html".into()) | |
2451 | { | |
2452 | let status = | |
2453 | Command::new("tidy").args(&tidy_args).arg(entry.path()).status().unwrap(); | |
2454 | // `tidy` returns 1 if it modified the file. | |
2455 | assert!(status.success() || status.code() == Some(1)); | |
2456 | } | |
8bb4bdeb | 2457 | } |
fc512014 | 2458 | }; |
6a06907d XL |
2459 | tidy_dir(out_dir); |
2460 | tidy_dir(&compare_dir); | |
fc512014 XL |
2461 | |
2462 | let pager = { | |
2463 | let output = Command::new("git").args(&["config", "--get", "core.pager"]).output().ok(); | |
2464 | output.and_then(|out| { | |
2465 | if out.status.success() { | |
2466 | Some(String::from_utf8(out.stdout).expect("invalid UTF8 in git pager")) | |
2467 | } else { | |
2468 | None | |
2469 | } | |
2470 | }) | |
2471 | }; | |
fc512014 | 2472 | |
6a06907d XL |
2473 | let diff_filename = format!("build/tmp/rustdoc-compare-{}.diff", std::process::id()); |
2474 | ||
c295e0f8 XL |
2475 | if !write_filtered_diff( |
2476 | &diff_filename, | |
2477 | out_dir, | |
2478 | &compare_dir, | |
2479 | self.config.verbose, | |
2480 | |file_type, extension| { | |
2481 | file_type.is_file() | |
6a06907d | 2482 | && (extension == Some("html".into()) || extension == Some("js".into())) |
c295e0f8 XL |
2483 | }, |
2484 | ) { | |
2485 | return; | |
6a06907d XL |
2486 | } |
2487 | ||
2488 | match self.config.color { | |
2489 | ColorConfig::AlwaysColor => colored::control::set_override(true), | |
2490 | ColorConfig::NeverColor => colored::control::set_override(false), | |
2491 | _ => {} | |
2492 | } | |
2493 | ||
2494 | if let Some(pager) = pager { | |
fc512014 XL |
2495 | let pager = pager.trim(); |
2496 | if self.config.verbose { | |
2497 | eprintln!("using pager {}", pager); | |
2498 | } | |
2499 | let output = Command::new(pager) | |
2500 | // disable paging; we want this to be non-interactive | |
2501 | .env("PAGER", "") | |
6a06907d | 2502 | .stdin(File::open(&diff_filename).unwrap()) |
fc512014 XL |
2503 | // Capture output and print it explicitly so it will in turn be |
2504 | // captured by libtest. | |
2505 | .output() | |
2506 | .unwrap(); | |
2507 | assert!(output.status.success()); | |
6a06907d XL |
2508 | println!("{}", String::from_utf8_lossy(&output.stdout)); |
2509 | eprintln!("{}", String::from_utf8_lossy(&output.stderr)); | |
fc512014 | 2510 | } else { |
6a06907d XL |
2511 | use colored::Colorize; |
2512 | eprintln!("warning: no pager configured, falling back to unified diff"); | |
fc512014 XL |
2513 | eprintln!( |
2514 | "help: try configuring a git pager (e.g. `delta`) with `git config --global core.pager delta`" | |
2515 | ); | |
6a06907d XL |
2516 | let mut out = io::stdout(); |
2517 | let mut diff = BufReader::new(File::open(&diff_filename).unwrap()); | |
2518 | let mut line = Vec::new(); | |
2519 | loop { | |
2520 | line.truncate(0); | |
2521 | match diff.read_until(b'\n', &mut line) { | |
2522 | Ok(0) => break, | |
2523 | Ok(_) => {} | |
2524 | Err(e) => eprintln!("ERROR: {:?}", e), | |
2525 | } | |
2526 | match String::from_utf8(line.clone()) { | |
2527 | Ok(line) => { | |
2528 | if line.starts_with("+") { | |
2529 | write!(&mut out, "{}", line.green()).unwrap(); | |
2530 | } else if line.starts_with("-") { | |
2531 | write!(&mut out, "{}", line.red()).unwrap(); | |
2532 | } else if line.starts_with("@") { | |
2533 | write!(&mut out, "{}", line.blue()).unwrap(); | |
2534 | } else { | |
2535 | out.write_all(line.as_bytes()).unwrap(); | |
2536 | } | |
2537 | } | |
2538 | Err(_) => { | |
2539 | write!(&mut out, "{}", String::from_utf8_lossy(&line).reversed()).unwrap(); | |
2540 | } | |
2541 | } | |
2542 | } | |
fc512014 | 2543 | }; |
fc512014 XL |
2544 | } |
2545 | ||
2546 | fn run_rustdoc_json_test(&self) { | |
2547 | //FIXME: Add bless option. | |
2548 | ||
2549 | assert!(self.revision.is_none(), "revisions not relevant here"); | |
2550 | ||
2551 | let out_dir = self.output_base_dir(); | |
2552 | let _ = fs::remove_dir_all(&out_dir); | |
2553 | create_dir_all(&out_dir).unwrap(); | |
2554 | ||
2555 | let proc_res = self.document(&out_dir); | |
2556 | if !proc_res.status.success() { | |
2557 | self.fatal_proc_rec("rustdoc failed!", &proc_res); | |
2558 | } | |
2559 | ||
2560 | let root = self.config.find_rust_src_root().unwrap(); | |
2561 | let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap()); | |
2562 | json_out.set_extension("json"); | |
2563 | let res = self.cmd2procres( | |
5869c6ff XL |
2564 | Command::new(self.config.jsondocck_path.as_ref().unwrap()) |
2565 | .arg("--doc-dir") | |
2566 | .arg(root.join(&out_dir)) | |
2567 | .arg("--template") | |
2568 | .arg(&self.testpaths.file), | |
fc512014 XL |
2569 | ); |
2570 | ||
2571 | if !res.status.success() { | |
5099ac24 FG |
2572 | self.fatal_proc_rec_with_ctx("jsondocck failed!", &res, |_| { |
2573 | println!("Rustdoc Output:"); | |
2574 | proc_res.print_info(); | |
2575 | }) | |
fc512014 XL |
2576 | } |
2577 | ||
5869c6ff XL |
2578 | let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap()); |
2579 | json_out.set_extension("json"); | |
f2b60f7d | 2580 | |
fc512014 | 2581 | let res = self.cmd2procres( |
f2b60f7d | 2582 | Command::new(self.config.jsondoclint_path.as_ref().unwrap()).arg(&json_out), |
fc512014 XL |
2583 | ); |
2584 | ||
2585 | if !res.status.success() { | |
f2b60f7d | 2586 | self.fatal_proc_rec("jsondoclint failed!", &res); |
8bb4bdeb XL |
2587 | } |
2588 | } | |
2589 | ||
ff7c6d11 XL |
2590 | fn get_lines<P: AsRef<Path>>( |
2591 | &self, | |
2592 | path: &P, | |
2593 | mut other_files: Option<&mut Vec<String>>, | |
2594 | ) -> Vec<usize> { | |
0731742a | 2595 | let content = fs::read_to_string(&path).unwrap(); |
8bb4bdeb | 2596 | let mut ignore = false; |
ff7c6d11 XL |
2597 | content |
2598 | .lines() | |
2599 | .enumerate() | |
2600 | .filter_map(|(line_nb, line)| { | |
0731742a XL |
2601 | if (line.trim_start().starts_with("pub mod ") |
2602 | || line.trim_start().starts_with("mod ")) | |
ff7c6d11 XL |
2603 | && line.ends_with(';') |
2604 | { | |
2605 | if let Some(ref mut other_files) = other_files { | |
2606 | other_files.push(line.rsplit("mod ").next().unwrap().replace(";", "")); | |
2607 | } | |
2608 | None | |
2609 | } else { | |
f2b60f7d | 2610 | let sline = line.rsplit("///").next().unwrap(); |
0731742a | 2611 | let line = sline.trim_start(); |
ff7c6d11 XL |
2612 | if line.starts_with("```") { |
2613 | if ignore { | |
2614 | ignore = false; | |
2615 | None | |
2616 | } else { | |
2617 | ignore = true; | |
2618 | Some(line_nb + 1) | |
2619 | } | |
2620 | } else { | |
2621 | None | |
2622 | } | |
2623 | } | |
2624 | }) | |
2625 | .collect() | |
8bb4bdeb XL |
2626 | } |
2627 | ||
2628 | fn check_rustdoc_test_option(&self, res: ProcRes) { | |
2629 | let mut other_files = Vec::new(); | |
2630 | let mut files: HashMap<String, Vec<usize>> = HashMap::new(); | |
2631 | let cwd = env::current_dir().unwrap(); | |
ff7c6d11 XL |
2632 | files.insert( |
2633 | self.testpaths | |
2634 | .file | |
2635 | .strip_prefix(&cwd) | |
2636 | .unwrap_or(&self.testpaths.file) | |
2637 | .to_str() | |
2638 | .unwrap() | |
2639 | .replace('\\', "/"), | |
2640 | self.get_lines(&self.testpaths.file, Some(&mut other_files)), | |
2641 | ); | |
8bb4bdeb XL |
2642 | for other_file in other_files { |
2643 | let mut path = self.testpaths.file.clone(); | |
2644 | path.set_file_name(&format!("{}.rs", other_file)); | |
ff7c6d11 | 2645 | files.insert( |
dfeec247 | 2646 | path.strip_prefix(&cwd).unwrap_or(&path).to_str().unwrap().replace('\\', "/"), |
ff7c6d11 XL |
2647 | self.get_lines(&path, None), |
2648 | ); | |
8bb4bdeb XL |
2649 | } |
2650 | ||
2651 | let mut tested = 0; | |
dfeec247 | 2652 | for _ in res.stdout.split('\n').filter(|s| s.starts_with("test ")).inspect(|s| { |
17df50a5 XL |
2653 | if let Some((left, right)) = s.split_once(" - ") { |
2654 | let path = left.rsplit("test ").next().unwrap(); | |
dfeec247 XL |
2655 | if let Some(ref mut v) = files.get_mut(&path.replace('\\', "/")) { |
2656 | tested += 1; | |
17df50a5 | 2657 | let mut iter = right.split("(line "); |
dfeec247 XL |
2658 | iter.next(); |
2659 | let line = iter | |
2660 | .next() | |
2661 | .unwrap_or(")") | |
2662 | .split(')') | |
2663 | .next() | |
2664 | .unwrap_or("0") | |
2665 | .parse() | |
2666 | .unwrap_or(0); | |
2667 | if let Ok(pos) = v.binary_search(&line) { | |
2668 | v.remove(pos); | |
2669 | } else { | |
2670 | self.fatal_proc_rec( | |
2671 | &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v), | |
2672 | &res, | |
2673 | ); | |
ff7c6d11 XL |
2674 | } |
2675 | } | |
dfeec247 XL |
2676 | } |
2677 | }) {} | |
8bb4bdeb XL |
2678 | if tested == 0 { |
2679 | self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res); | |
2680 | } else { | |
2681 | for (entry, v) in &files { | |
041b39d2 | 2682 | if !v.is_empty() { |
ff7c6d11 XL |
2683 | self.fatal_proc_rec( |
2684 | &format!( | |
2685 | "Not found test at line{} \"{}\":{:?}", | |
2686 | if v.len() > 1 { "s" } else { "" }, | |
2687 | entry, | |
2688 | v | |
2689 | ), | |
2690 | &res, | |
2691 | ); | |
8bb4bdeb XL |
2692 | } |
2693 | } | |
a7813a04 XL |
2694 | } |
2695 | } | |
2696 | ||
2697 | fn run_codegen_units_test(&self) { | |
2698 | assert!(self.revision.is_none(), "revisions not relevant here"); | |
2699 | ||
487cf647 | 2700 | let proc_res = self.compile_test(WillExecute::No, Emit::None); |
a7813a04 XL |
2701 | |
2702 | if !proc_res.status.success() { | |
2703 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
2704 | } | |
2705 | ||
60c5eb7d | 2706 | self.check_no_compiler_crash(&proc_res, self.props.should_ice); |
a7813a04 | 2707 | |
3dfed10e XL |
2708 | const PREFIX: &str = "MONO_ITEM "; |
2709 | const CGU_MARKER: &str = "@@"; | |
a7813a04 | 2710 | |
94b46f34 | 2711 | let actual: Vec<MonoItem> = proc_res |
a7813a04 XL |
2712 | .stdout |
2713 | .lines() | |
2714 | .filter(|line| line.starts_with(PREFIX)) | |
b7449926 | 2715 | .map(|line| str_to_mono_item(line, true)) |
a7813a04 XL |
2716 | .collect(); |
2717 | ||
94b46f34 | 2718 | let expected: Vec<MonoItem> = errors::load_errors(&self.testpaths.file, None) |
a7813a04 | 2719 | .iter() |
b7449926 | 2720 | .map(|e| str_to_mono_item(&e.msg[..], false)) |
a7813a04 XL |
2721 | .collect(); |
2722 | ||
2723 | let mut missing = Vec::new(); | |
2724 | let mut wrong_cgus = Vec::new(); | |
2725 | ||
2726 | for expected_item in &expected { | |
ff7c6d11 | 2727 | let actual_item_with_same_name = actual.iter().find(|ti| ti.name == expected_item.name); |
a7813a04 XL |
2728 | |
2729 | if let Some(actual_item) = actual_item_with_same_name { | |
041b39d2 XL |
2730 | if !expected_item.codegen_units.is_empty() && |
2731 | // Also check for codegen units | |
ff7c6d11 XL |
2732 | expected_item.codegen_units != actual_item.codegen_units |
2733 | { | |
041b39d2 | 2734 | wrong_cgus.push((expected_item.clone(), actual_item.clone())); |
a7813a04 XL |
2735 | } |
2736 | } else { | |
2737 | missing.push(expected_item.string.clone()); | |
2738 | } | |
2739 | } | |
2740 | ||
ff7c6d11 XL |
2741 | let unexpected: Vec<_> = actual |
2742 | .iter() | |
2743 | .filter(|acgu| !expected.iter().any(|ecgu| acgu.name == ecgu.name)) | |
2744 | .map(|acgu| acgu.string.clone()) | |
2745 | .collect(); | |
a7813a04 XL |
2746 | |
2747 | if !missing.is_empty() { | |
2748 | missing.sort(); | |
2749 | ||
2750 | println!("\nThese items should have been contained but were not:\n"); | |
2751 | ||
2752 | for item in &missing { | |
2753 | println!("{}", item); | |
2754 | } | |
2755 | ||
2756 | println!("\n"); | |
2757 | } | |
2758 | ||
2759 | if !unexpected.is_empty() { | |
2760 | let sorted = { | |
2761 | let mut sorted = unexpected.clone(); | |
2762 | sorted.sort(); | |
2763 | sorted | |
2764 | }; | |
2765 | ||
2766 | println!("\nThese items were contained but should not have been:\n"); | |
2767 | ||
2768 | for item in sorted { | |
2769 | println!("{}", item); | |
2770 | } | |
2771 | ||
2772 | println!("\n"); | |
2773 | } | |
2774 | ||
2775 | if !wrong_cgus.is_empty() { | |
2776 | wrong_cgus.sort_by_key(|pair| pair.0.name.clone()); | |
2777 | println!("\nThe following items were assigned to wrong codegen units:\n"); | |
2778 | ||
2779 | for &(ref expected_item, ref actual_item) in &wrong_cgus { | |
2780 | println!("{}", expected_item.name); | |
dfeec247 XL |
2781 | println!(" expected: {}", codegen_units_to_str(&expected_item.codegen_units)); |
2782 | println!(" actual: {}", codegen_units_to_str(&actual_item.codegen_units)); | |
e1599b0c | 2783 | println!(); |
a7813a04 XL |
2784 | } |
2785 | } | |
2786 | ||
ff7c6d11 | 2787 | if !(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty()) { |
a7813a04 XL |
2788 | panic!(); |
2789 | } | |
2790 | ||
2791 | #[derive(Clone, Eq, PartialEq)] | |
94b46f34 | 2792 | struct MonoItem { |
a7813a04 XL |
2793 | name: String, |
2794 | codegen_units: HashSet<String>, | |
2795 | string: String, | |
2796 | } | |
2797 | ||
94b46f34 | 2798 | // [MONO_ITEM] name [@@ (cgu)+] |
b7449926 | 2799 | fn str_to_mono_item(s: &str, cgu_has_crate_disambiguator: bool) -> MonoItem { |
dfeec247 | 2800 | let s = if s.starts_with(PREFIX) { (&s[PREFIX.len()..]).trim() } else { s.trim() }; |
a7813a04 | 2801 | |
b7449926 | 2802 | let full_string = format!("{}{}", PREFIX, s); |
a7813a04 | 2803 | |
dfeec247 XL |
2804 | let parts: Vec<&str> = |
2805 | s.split(CGU_MARKER).map(str::trim).filter(|s| !s.is_empty()).collect(); | |
a7813a04 XL |
2806 | |
2807 | let name = parts[0].trim(); | |
2808 | ||
2809 | let cgus = if parts.len() > 1 { | |
2810 | let cgus_str = parts[1]; | |
2811 | ||
ff7c6d11 XL |
2812 | cgus_str |
2813 | .split(' ') | |
2814 | .map(str::trim) | |
2815 | .filter(|s| !s.is_empty()) | |
b7449926 XL |
2816 | .map(|s| { |
2817 | if cgu_has_crate_disambiguator { | |
ba9703b0 | 2818 | remove_crate_disambiguators_from_set_of_cgu_names(s) |
b7449926 XL |
2819 | } else { |
2820 | s.to_string() | |
2821 | } | |
2822 | }) | |
ff7c6d11 XL |
2823 | .collect() |
2824 | } else { | |
a7813a04 XL |
2825 | HashSet::new() |
2826 | }; | |
2827 | ||
dfeec247 | 2828 | MonoItem { name: name.to_owned(), codegen_units: cgus, string: full_string } |
a7813a04 XL |
2829 | } |
2830 | ||
ff7c6d11 | 2831 | fn codegen_units_to_str(cgus: &HashSet<String>) -> String { |
a7813a04 XL |
2832 | let mut cgus: Vec<_> = cgus.iter().collect(); |
2833 | cgus.sort(); | |
2834 | ||
2835 | let mut string = String::new(); | |
2836 | for cgu in cgus { | |
2837 | string.push_str(&cgu[..]); | |
2838 | string.push_str(" "); | |
2839 | } | |
2840 | ||
2841 | string | |
2842 | } | |
b7449926 XL |
2843 | |
2844 | // Given a cgu-name-prefix of the form <crate-name>.<crate-disambiguator> or | |
2845 | // the form <crate-name1>.<crate-disambiguator1>-in-<crate-name2>.<crate-disambiguator2>, | |
2846 | // remove all crate-disambiguators. | |
2847 | fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String { | |
2848 | lazy_static! { | |
dfeec247 XL |
2849 | static ref RE: Regex = |
2850 | Regex::new(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?") | |
2851 | .unwrap(); | |
b7449926 XL |
2852 | } |
2853 | ||
dfeec247 XL |
2854 | let captures = |
2855 | RE.captures(cgu).unwrap_or_else(|| panic!("invalid cgu name encountered: {}", cgu)); | |
b7449926 XL |
2856 | |
2857 | let mut new_name = cgu.to_owned(); | |
2858 | ||
2859 | if let Some(d2) = captures.name("d2") { | |
dfeec247 | 2860 | new_name.replace_range(d2.start()..d2.end(), ""); |
b7449926 XL |
2861 | } |
2862 | ||
2863 | let d1 = captures.name("d1").unwrap(); | |
dfeec247 | 2864 | new_name.replace_range(d1.start()..d1.end(), ""); |
b7449926 XL |
2865 | |
2866 | new_name | |
2867 | } | |
ba9703b0 XL |
2868 | |
2869 | // The name of merged CGUs is constructed as the names of the original | |
2870 | // CGUs joined with "--". This function splits such composite CGU names | |
2871 | // and handles each component individually. | |
2872 | fn remove_crate_disambiguators_from_set_of_cgu_names(cgus: &str) -> String { | |
2873 | cgus.split("--") | |
2874 | .map(|cgu| remove_crate_disambiguator_from_cgu(cgu)) | |
2875 | .collect::<Vec<_>>() | |
2876 | .join("--") | |
2877 | } | |
a7813a04 XL |
2878 | } |
2879 | ||
2880 | fn init_incremental_test(&self) { | |
2881 | // (See `run_incremental_test` for an overview of how incremental tests work.) | |
2882 | ||
2883 | // Before any of the revisions have executed, create the | |
2884 | // incremental workproduct directory. Delete any old | |
2885 | // incremental work products that may be there from prior | |
2886 | // runs. | |
c295e0f8 | 2887 | let incremental_dir = self.props.incremental_dir.as_ref().unwrap(); |
a7813a04 | 2888 | if incremental_dir.exists() { |
9e0c209e SL |
2889 | // Canonicalizing the path will convert it to the //?/ format |
2890 | // on Windows, which enables paths longer than 260 character | |
2891 | let canonicalized = incremental_dir.canonicalize().unwrap(); | |
2892 | fs::remove_dir_all(canonicalized).unwrap(); | |
a7813a04 XL |
2893 | } |
2894 | fs::create_dir_all(&incremental_dir).unwrap(); | |
2895 | ||
2896 | if self.config.verbose { | |
c295e0f8 | 2897 | println!("init_incremental_test: incremental_dir={}", incremental_dir.display()); |
a7813a04 XL |
2898 | } |
2899 | } | |
2900 | ||
2901 | fn run_incremental_test(&self) { | |
2902 | // Basic plan for a test incremental/foo/bar.rs: | |
2903 | // - load list of revisions rpass1, cfail2, rpass3 | |
94b46f34 | 2904 | // - each should begin with `rpass`, `cfail`, or `rfail` |
a7813a04 XL |
2905 | // - if `rpass`, expect compile and execution to succeed |
2906 | // - if `cfail`, expect compilation to fail | |
2907 | // - if `rfail`, expect execution to fail | |
2908 | // - create a directory build/foo/bar.incremental | |
ba9703b0 | 2909 | // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass1 |
a7813a04 | 2910 | // - because name of revision starts with "rpass", expect success |
ba9703b0 | 2911 | // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C cfail2 |
a7813a04 XL |
2912 | // - because name of revision starts with "cfail", expect an error |
2913 | // - load expected errors as usual, but filter for those that end in `[rfail2]` | |
ba9703b0 | 2914 | // - compile foo/bar.rs with -C incremental=.../foo/bar.incremental and -C rpass3 |
a7813a04 XL |
2915 | // - because name of revision starts with "rpass", expect success |
2916 | // - execute build/foo/bar.exe and save output | |
2917 | // | |
2918 | // FIXME -- use non-incremental mode as an oracle? That doesn't apply | |
2919 | // to #[rustc_dirty] and clean tests I guess | |
2920 | ||
dfeec247 | 2921 | let revision = self.revision.expect("incremental tests require a list of revisions"); |
a7813a04 XL |
2922 | |
2923 | // Incremental workproduct directory should have already been created. | |
c295e0f8 | 2924 | let incremental_dir = self.props.incremental_dir.as_ref().unwrap(); |
dfeec247 | 2925 | assert!(incremental_dir.exists(), "init_incremental_test failed to create incremental dir"); |
a7813a04 | 2926 | |
a7813a04 | 2927 | if self.config.verbose { |
c295e0f8 | 2928 | print!("revision={:?} props={:#?}", revision, self.props); |
a7813a04 XL |
2929 | } |
2930 | ||
2931 | if revision.starts_with("rpass") { | |
c295e0f8 XL |
2932 | if self.props.should_ice { |
2933 | self.fatal("can only use should-ice in cfail tests"); | |
60c5eb7d | 2934 | } |
c295e0f8 | 2935 | self.run_rpass_test(); |
a7813a04 | 2936 | } else if revision.starts_with("rfail") { |
c295e0f8 XL |
2937 | if self.props.should_ice { |
2938 | self.fatal("can only use should-ice in cfail tests"); | |
60c5eb7d | 2939 | } |
c295e0f8 | 2940 | self.run_rfail_test(); |
a7813a04 | 2941 | } else if revision.starts_with("cfail") { |
c295e0f8 | 2942 | self.run_cfail_test(); |
a7813a04 | 2943 | } else { |
c295e0f8 | 2944 | self.fatal("revision name must begin with rpass, rfail, or cfail"); |
a7813a04 XL |
2945 | } |
2946 | } | |
2947 | ||
a7813a04 | 2948 | fn run_rmake_test(&self) { |
a7813a04 | 2949 | let cwd = env::current_dir().unwrap(); |
9c376795 | 2950 | let src_root = self.config.src_base.parent().unwrap().parent().unwrap(); |
a7813a04 XL |
2951 | let src_root = cwd.join(&src_root); |
2952 | ||
94b46f34 | 2953 | let tmpdir = cwd.join(self.output_base_name()); |
a7813a04 XL |
2954 | if tmpdir.exists() { |
2955 | self.aggressive_rm_rf(&tmpdir).unwrap(); | |
2956 | } | |
cc61c64b | 2957 | create_dir_all(&tmpdir).unwrap(); |
a7813a04 | 2958 | |
476ff2be | 2959 | let host = &self.config.host; |
48663c56 | 2960 | let make = if host.contains("dragonfly") |
94b46f34 XL |
2961 | || host.contains("freebsd") |
2962 | || host.contains("netbsd") | |
ff7c6d11 XL |
2963 | || host.contains("openbsd") |
2964 | { | |
476ff2be SL |
2965 | "gmake" |
2966 | } else { | |
2967 | "make" | |
2968 | }; | |
2969 | ||
2970 | let mut cmd = Command::new(make); | |
a7813a04 | 2971 | cmd.current_dir(&self.testpaths.file) |
ff7c6d11 XL |
2972 | .stdout(Stdio::piped()) |
2973 | .stderr(Stdio::piped()) | |
2974 | .env("TARGET", &self.config.target) | |
04454e1e | 2975 | .env("PYTHON", &self.config.python) |
ff7c6d11 XL |
2976 | .env("S", src_root) |
2977 | .env("RUST_BUILD_STAGE", &self.config.stage_id) | |
2978 | .env("RUSTC", cwd.join(&self.config.rustc_path)) | |
ff7c6d11 XL |
2979 | .env("TMPDIR", &tmpdir) |
2980 | .env("LD_LIB_PATH_ENVVAR", dylib_env_var()) | |
2981 | .env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path)) | |
2982 | .env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path)) | |
2983 | .env("LLVM_COMPONENTS", &self.config.llvm_components) | |
0531ce1d | 2984 | // We for sure don't want these tests to run in parallel, so make |
0731742a | 2985 | // sure they don't have access to these vars if we run via `make` |
0531ce1d XL |
2986 | // at the top level |
2987 | .env_remove("MAKEFLAGS") | |
2988 | .env_remove("MFLAGS") | |
2989 | .env_remove("CARGO_MAKEFLAGS"); | |
2990 | ||
2991 | if let Some(ref rustdoc) = self.config.rustdoc_path { | |
2992 | cmd.env("RUSTDOC", cwd.join(rustdoc)); | |
2993 | } | |
2994 | ||
3dfed10e XL |
2995 | if let Some(ref rust_demangler) = self.config.rust_demangler_path { |
2996 | cmd.env("RUST_DEMANGLER", cwd.join(rust_demangler)); | |
2997 | } | |
2998 | ||
0531ce1d XL |
2999 | if let Some(ref node) = self.config.nodejs { |
3000 | cmd.env("NODE", node); | |
3001 | } | |
a7813a04 | 3002 | |
abe05a73 XL |
3003 | if let Some(ref linker) = self.config.linker { |
3004 | cmd.env("RUSTC_LINKER", linker); | |
3005 | } | |
3006 | ||
9fa01778 XL |
3007 | if let Some(ref clang) = self.config.run_clang_based_tests_with { |
3008 | cmd.env("CLANG", clang); | |
3009 | } | |
3010 | ||
48663c56 XL |
3011 | if let Some(ref filecheck) = self.config.llvm_filecheck { |
3012 | cmd.env("LLVM_FILECHECK", filecheck); | |
3013 | } | |
3014 | ||
3015 | if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir { | |
3016 | cmd.env("LLVM_BIN_DIR", llvm_bin_dir); | |
3017 | } | |
3018 | ||
487cf647 FG |
3019 | if let Some(ref remote_test_client) = self.config.remote_test_client { |
3020 | cmd.env("REMOTE_TEST_CLIENT", remote_test_client); | |
3021 | } | |
3022 | ||
32a655c1 SL |
3023 | // We don't want RUSTFLAGS set from the outside to interfere with |
3024 | // compiler flags set in the test cases: | |
3025 | cmd.env_remove("RUSTFLAGS"); | |
3026 | ||
48663c56 XL |
3027 | // Use dynamic musl for tests because static doesn't allow creating dylibs |
3028 | if self.config.host.contains("musl") { | |
dfeec247 | 3029 | cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static").env("IS_MUSL_HOST", "1"); |
48663c56 XL |
3030 | } |
3031 | ||
1b1a35ee XL |
3032 | if self.config.bless { |
3033 | cmd.env("RUSTC_BLESS_TEST", "--bless"); | |
3034 | // Assume this option is active if the environment variable is "defined", with _any_ value. | |
3035 | // As an example, a `Makefile` can use this option by: | |
3036 | // | |
3037 | // ifdef RUSTC_BLESS_TEST | |
3038 | // cp "$(TMPDIR)"/actual_something.ext expected_something.ext | |
3039 | // else | |
3040 | // $(DIFF) expected_something.ext "$(TMPDIR)"/actual_something.ext | |
3041 | // endif | |
3042 | } | |
3043 | ||
0531ce1d | 3044 | if self.config.target.contains("msvc") && self.config.cc != "" { |
a7813a04 XL |
3045 | // We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe` |
3046 | // and that `lib.exe` lives next to it. | |
3047 | let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe"); | |
3048 | ||
3049 | // MSYS doesn't like passing flags of the form `/foo` as it thinks it's | |
3050 | // a path and instead passes `C:\msys64\foo`, so convert all | |
3051 | // `/`-arguments to MSVC here to `-` arguments. | |
94b46f34 XL |
3052 | let cflags = self |
3053 | .config | |
ff7c6d11 XL |
3054 | .cflags |
3055 | .split(' ') | |
3056 | .map(|s| s.replace("/", "-")) | |
3057 | .collect::<Vec<_>>() | |
3058 | .join(" "); | |
5099ac24 FG |
3059 | let cxxflags = self |
3060 | .config | |
3061 | .cxxflags | |
3062 | .split(' ') | |
3063 | .map(|s| s.replace("/", "-")) | |
3064 | .collect::<Vec<_>>() | |
3065 | .join(" "); | |
a7813a04 XL |
3066 | |
3067 | cmd.env("IS_MSVC", "1") | |
ff7c6d11 XL |
3068 | .env("IS_WINDOWS", "1") |
3069 | .env("MSVC_LIB", format!("'{}' -nologo", lib.display())) | |
3070 | .env("CC", format!("'{}' {}", self.config.cc, cflags)) | |
5099ac24 | 3071 | .env("CXX", format!("'{}' {}", &self.config.cxx, cxxflags)); |
a7813a04 XL |
3072 | } else { |
3073 | cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags)) | |
5099ac24 | 3074 | .env("CXX", format!("{} {}", self.config.cxx, self.config.cxxflags)) |
ff7c6d11 | 3075 | .env("AR", &self.config.ar); |
c30ab7b3 SL |
3076 | |
3077 | if self.config.target.contains("windows") { | |
3078 | cmd.env("IS_WINDOWS", "1"); | |
3079 | } | |
a7813a04 XL |
3080 | } |
3081 | ||
923072b8 | 3082 | let output = self.read2_abbreviated(cmd.spawn().expect("failed to spawn `make`")); |
a7813a04 XL |
3083 | if !output.status.success() { |
3084 | let res = ProcRes { | |
7cac9316 | 3085 | status: output.status, |
a7813a04 XL |
3086 | stdout: String::from_utf8_lossy(&output.stdout).into_owned(), |
3087 | stderr: String::from_utf8_lossy(&output.stderr).into_owned(), | |
3088 | cmdline: format!("{:?}", cmd), | |
3089 | }; | |
3090 | self.fatal_proc_rec("make failed", &res); | |
3091 | } | |
3092 | } | |
3093 | ||
3094 | fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> { | |
9e0c209e SL |
3095 | for e in path.read_dir()? { |
3096 | let entry = e?; | |
a7813a04 | 3097 | let path = entry.path(); |
9e0c209e SL |
3098 | if entry.file_type()?.is_dir() { |
3099 | self.aggressive_rm_rf(&path)?; | |
a7813a04 XL |
3100 | } else { |
3101 | // Remove readonly files as well on windows (by default we can't) | |
9e0c209e | 3102 | fs::remove_file(&path).or_else(|e| { |
a7813a04 | 3103 | if cfg!(windows) && e.kind() == io::ErrorKind::PermissionDenied { |
9e0c209e | 3104 | let mut meta = entry.metadata()?.permissions(); |
a7813a04 | 3105 | meta.set_readonly(false); |
9e0c209e | 3106 | fs::set_permissions(&path, meta)?; |
a7813a04 XL |
3107 | fs::remove_file(&path) |
3108 | } else { | |
3109 | Err(e) | |
3110 | } | |
9e0c209e | 3111 | })?; |
a7813a04 XL |
3112 | } |
3113 | } | |
3114 | fs::remove_dir(path) | |
3115 | } | |
3116 | ||
9fa01778 XL |
3117 | fn run_js_doc_test(&self) { |
3118 | if let Some(nodejs) = &self.config.nodejs { | |
3119 | let out_dir = self.output_base_dir(); | |
3120 | ||
3121 | self.document(&out_dir); | |
3122 | ||
3123 | let root = self.config.find_rust_src_root().unwrap(); | |
ba9703b0 XL |
3124 | let file_stem = |
3125 | self.testpaths.file.file_stem().and_then(|f| f.to_str()).expect("no file stem"); | |
9fa01778 XL |
3126 | let res = self.cmd2procres( |
3127 | Command::new(&nodejs) | |
3128 | .arg(root.join("src/tools/rustdoc-js/tester.js")) | |
ba9703b0 XL |
3129 | .arg("--doc-folder") |
3130 | .arg(out_dir) | |
3131 | .arg("--crate-name") | |
3132 | .arg(file_stem.replace("-", "_")) | |
3133 | .arg("--test-file") | |
3134 | .arg(self.testpaths.file.with_extension("js")), | |
9fa01778 XL |
3135 | ); |
3136 | if !res.status.success() { | |
3137 | self.fatal_proc_rec("rustdoc-js test failed!", &res); | |
3138 | } | |
3139 | } else { | |
3140 | self.fatal("no nodeJS"); | |
3141 | } | |
3142 | } | |
3143 | ||
dfeec247 XL |
3144 | fn load_compare_outputs( |
3145 | &self, | |
3146 | proc_res: &ProcRes, | |
3147 | output_kind: TestOutput, | |
3148 | explicit_format: bool, | |
3149 | ) -> usize { | |
f2b60f7d | 3150 | let stderr_bits = format!("{}bit.stderr", self.config.get_pointer_width()); |
e1599b0c | 3151 | let (stderr_kind, stdout_kind) = match output_kind { |
cdc7bbd5 XL |
3152 | TestOutput::Compile => ( |
3153 | { | |
3154 | if self.props.stderr_per_bitwidth { &stderr_bits } else { UI_STDERR } | |
3155 | }, | |
3156 | UI_STDOUT, | |
3157 | ), | |
dfeec247 | 3158 | TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT), |
e1599b0c XL |
3159 | }; |
3160 | ||
3161 | let expected_stderr = self.load_expected_output(stderr_kind); | |
3162 | let expected_stdout = self.load_expected_output(stdout_kind); | |
3163 | ||
3164 | let normalized_stdout = match output_kind { | |
3165 | TestOutput::Run if self.config.remote_test_client.is_some() => { | |
3166 | // When tests are run using the remote-test-client, the string | |
3167 | // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"' | |
3168 | // is printed to stdout by the client and then captured in the ProcRes, | |
3169 | // so it needs to be removed when comparing the run-pass test execution output | |
3170 | lazy_static! { | |
3171 | static ref REMOTE_TEST_RE: Regex = Regex::new( | |
94222f64 | 3172 | "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n" |
dfeec247 XL |
3173 | ) |
3174 | .unwrap(); | |
e1599b0c | 3175 | } |
dfeec247 XL |
3176 | REMOTE_TEST_RE |
3177 | .replace( | |
3178 | &self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout), | |
3179 | "", | |
3180 | ) | |
3181 | .to_string() | |
3182 | } | |
3183 | _ => self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout), | |
e1599b0c XL |
3184 | }; |
3185 | ||
3186 | let stderr = if explicit_format { | |
3187 | proc_res.stderr.clone() | |
3188 | } else { | |
3189 | json::extract_rendered(&proc_res.stderr) | |
3190 | }; | |
3191 | ||
3192 | let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr); | |
3193 | let mut errors = 0; | |
3194 | match output_kind { | |
3195 | TestOutput::Compile => { | |
3196 | if !self.props.dont_check_compiler_stdout { | |
cdc7bbd5 XL |
3197 | errors += |
3198 | self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout); | |
e1599b0c XL |
3199 | } |
3200 | if !self.props.dont_check_compiler_stderr { | |
cdc7bbd5 XL |
3201 | errors += |
3202 | self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr); | |
e1599b0c XL |
3203 | } |
3204 | } | |
3205 | TestOutput::Run => { | |
3206 | errors += self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout); | |
3207 | errors += self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr); | |
3208 | } | |
3209 | } | |
3210 | errors | |
3211 | } | |
3212 | ||
a7813a04 | 3213 | fn run_ui_test(&self) { |
dfeec247 XL |
3214 | if let Some(FailMode::Build) = self.props.fail_mode { |
3215 | // Make sure a build-fail test cannot fail due to failing analysis (e.g. typeck). | |
3216 | let pm = Some(PassMode::Check); | |
487cf647 | 3217 | let proc_res = self.compile_test_general(WillExecute::No, Emit::Metadata, pm); |
dfeec247 XL |
3218 | self.check_if_test_should_compile(&proc_res, pm); |
3219 | } | |
3220 | ||
3221 | let pm = self.pass_mode(); | |
3222 | let should_run = self.should_run(pm); | |
3223 | let emit_metadata = self.should_emit_metadata(pm); | |
3224 | let proc_res = self.compile_test(should_run, emit_metadata); | |
3225 | self.check_if_test_should_compile(&proc_res, pm); | |
3226 | ||
ff7c6d11 XL |
3227 | // if the user specified a format in the ui test |
3228 | // print the output to the stderr file, otherwise extract | |
3229 | // the rendered error messages from json and print them | |
dfeec247 | 3230 | let explicit = self.props.compile_flags.iter().any(|s| s.contains("--error-format")); |
a7813a04 | 3231 | |
83c7162d | 3232 | let expected_fixed = self.load_expected_output(UI_FIXED); |
a7813a04 | 3233 | |
923072b8 | 3234 | self.check_and_prune_duplicate_outputs(&proc_res, &[], &[]); |
94b46f34 | 3235 | |
e1599b0c | 3236 | let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit); |
29967ef6 | 3237 | let rustfix_input = json::rustfix_diagnostics_only(&proc_res.stderr); |
e1599b0c | 3238 | |
83c7162d XL |
3239 | if self.config.compare_mode.is_some() { |
3240 | // don't test rustfix with nll right now | |
532ac7d7 XL |
3241 | } else if self.config.rustfix_coverage { |
3242 | // Find out which tests have `MachineApplicable` suggestions but are missing | |
3243 | // `run-rustfix` or `run-rustfix-only-machine-applicable` headers. | |
3244 | // | |
3245 | // This will return an empty `Vec` in case the executed test file has a | |
3246 | // `compile-flags: --error-format=xxxx` header with a value other than `json`. | |
3247 | let suggestions = get_suggestions_from_json( | |
29967ef6 | 3248 | &rustfix_input, |
532ac7d7 | 3249 | &HashSet::new(), |
dfeec247 XL |
3250 | Filter::MachineApplicableOnly, |
3251 | ) | |
3252 | .unwrap_or_default(); | |
3dfed10e | 3253 | if !suggestions.is_empty() |
532ac7d7 | 3254 | && !self.props.run_rustfix |
dfeec247 XL |
3255 | && !self.props.rustfix_only_machine_applicable |
3256 | { | |
3257 | let mut coverage_file_path = self.config.build_base.clone(); | |
3258 | coverage_file_path.push("rustfix_missing_coverage.txt"); | |
3259 | debug!("coverage_file_path: {}", coverage_file_path.display()); | |
3260 | ||
3261 | let mut file = OpenOptions::new() | |
3262 | .create(true) | |
3263 | .append(true) | |
3264 | .open(coverage_file_path.as_path()) | |
3265 | .expect("could not create or open file"); | |
3266 | ||
3dfed10e | 3267 | if writeln!(file, "{}", self.testpaths.file.display()).is_err() { |
dfeec247 XL |
3268 | panic!("couldn't write to {}", coverage_file_path.display()); |
3269 | } | |
532ac7d7 | 3270 | } |
83c7162d XL |
3271 | } else if self.props.run_rustfix { |
3272 | // Apply suggestions from rustc to the code itself | |
dfeec247 | 3273 | let unfixed_code = self.load_expected_output_from_path(&self.testpaths.file).unwrap(); |
8faf50e0 | 3274 | let suggestions = get_suggestions_from_json( |
29967ef6 | 3275 | &rustfix_input, |
8faf50e0 | 3276 | &HashSet::new(), |
b7449926 XL |
3277 | if self.props.rustfix_only_machine_applicable { |
3278 | Filter::MachineApplicableOnly | |
3279 | } else { | |
3280 | Filter::Everything | |
3281 | }, | |
dfeec247 XL |
3282 | ) |
3283 | .unwrap(); | |
136023e0 XL |
3284 | let fixed_code = apply_suggestions(&unfixed_code, &suggestions).unwrap_or_else(|e| { |
3285 | panic!( | |
3286 | "failed to apply suggestions for {:?} with rustfix: {}", | |
3287 | self.testpaths.file, e | |
3288 | ) | |
3dfed10e | 3289 | }); |
83c7162d XL |
3290 | |
3291 | errors += self.compare_output("fixed", &fixed_code, &expected_fixed); | |
3292 | } else if !expected_fixed.is_empty() { | |
94b46f34 XL |
3293 | panic!( |
3294 | "the `// run-rustfix` directive wasn't found but a `*.fixed` \ | |
3295 | file was found" | |
3296 | ); | |
83c7162d XL |
3297 | } |
3298 | ||
a7813a04 | 3299 | if errors > 0 { |
94b46f34 | 3300 | println!("To update references, rerun the tests and pass the `--bless` flag"); |
dfeec247 XL |
3301 | let relative_path_to_file = |
3302 | self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap()); | |
ff7c6d11 | 3303 | println!( |
94b46f34 XL |
3304 | "To only update this specific test, also pass `--test-args {}`", |
3305 | relative_path_to_file.display(), | |
ff7c6d11 XL |
3306 | ); |
3307 | self.fatal_proc_rec( | |
3308 | &format!("{} errors occurred comparing output.", errors), | |
3309 | &proc_res, | |
3310 | ); | |
a7813a04 | 3311 | } |
7cac9316 | 3312 | |
ff7c6d11 XL |
3313 | let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); |
3314 | ||
dfeec247 | 3315 | if let WillExecute::Yes = should_run { |
7cac9316 | 3316 | let proc_res = self.exec_compiled_test(); |
e1599b0c XL |
3317 | let run_output_errors = if self.props.check_run_results { |
3318 | self.load_compare_outputs(&proc_res, TestOutput::Run, explicit) | |
3319 | } else { | |
3320 | 0 | |
3321 | }; | |
3322 | if run_output_errors > 0 { | |
3323 | self.fatal_proc_rec( | |
dfeec247 | 3324 | &format!("{} errors occurred comparing run output.", run_output_errors), |
e1599b0c XL |
3325 | &proc_res, |
3326 | ); | |
3327 | } | |
dfeec247 | 3328 | if self.should_run_successfully(pm) { |
e74abb32 XL |
3329 | if !proc_res.status.success() { |
3330 | self.fatal_proc_rec("test run failed!", &proc_res); | |
3331 | } | |
c295e0f8 XL |
3332 | } else if proc_res.status.success() { |
3333 | self.fatal_proc_rec("test run succeeded!", &proc_res); | |
e74abb32 | 3334 | } |
c295e0f8 | 3335 | |
064997fb FG |
3336 | if !self.props.error_patterns.is_empty() || !self.props.regex_error_patterns.is_empty() |
3337 | { | |
e74abb32 | 3338 | // "// error-pattern" comments |
064997fb FG |
3339 | let output_to_check = self.get_output(&proc_res); |
3340 | self.check_all_error_patterns(&output_to_check, &proc_res, pm); | |
7cac9316 XL |
3341 | } |
3342 | } | |
b7449926 | 3343 | |
dfeec247 XL |
3344 | debug!( |
3345 | "run_ui_test: explicit={:?} config.compare_mode={:?} expected_errors={:?} \ | |
b7449926 | 3346 | proc_res.status={:?} props.error_patterns={:?}", |
dfeec247 XL |
3347 | explicit, |
3348 | self.config.compare_mode, | |
3349 | expected_errors, | |
3350 | proc_res.status, | |
3351 | self.props.error_patterns | |
3352 | ); | |
83c7162d | 3353 | if !explicit && self.config.compare_mode.is_none() { |
064997fb FG |
3354 | let check_patterns = should_run == WillExecute::No |
3355 | && (!self.props.error_patterns.is_empty() | |
3356 | || !self.props.regex_error_patterns.is_empty()); | |
60c5eb7d | 3357 | |
dfeec247 | 3358 | let check_annotations = !check_patterns || !expected_errors.is_empty(); |
60c5eb7d XL |
3359 | |
3360 | if check_patterns { | |
e74abb32 | 3361 | // "// error-pattern" comments |
064997fb FG |
3362 | let output_to_check = self.get_output(&proc_res); |
3363 | self.check_all_error_patterns(&output_to_check, &proc_res, pm); | |
e74abb32 | 3364 | } |
60c5eb7d XL |
3365 | |
3366 | if check_annotations { | |
e74abb32 XL |
3367 | // "//~ERROR comments" |
3368 | self.check_expected_errors(expected_errors, &proc_res); | |
ff7c6d11 XL |
3369 | } |
3370 | } | |
83c7162d XL |
3371 | |
3372 | if self.props.run_rustfix && self.config.compare_mode.is_none() { | |
3373 | // And finally, compile the fixed code and make sure it both | |
3374 | // succeeds and has no diagnostics. | |
487cf647 | 3375 | let rustc = self.make_compile_args( |
83c7162d XL |
3376 | &self.testpaths.file.with_extension(UI_FIXED), |
3377 | TargetLocation::ThisFile(self.make_exe_name()), | |
dfeec247 | 3378 | emit_metadata, |
74b04a01 | 3379 | AllowUnused::No, |
487cf647 | 3380 | LinkToAux::Yes, |
83c7162d | 3381 | ); |
83c7162d XL |
3382 | let res = self.compose_and_run_compiler(rustc, None); |
3383 | if !res.status.success() { | |
3384 | self.fatal_proc_rec("failed to compile fixed code", &res); | |
3385 | } | |
c295e0f8 XL |
3386 | if !res.stderr.is_empty() |
3387 | && !self.props.rustfix_only_machine_applicable | |
3388 | && !json::rustfix_diagnostics_only(&res.stderr).is_empty() | |
3389 | { | |
3390 | self.fatal_proc_rec("fixed code is still producing diagnostics", &res); | |
83c7162d XL |
3391 | } |
3392 | } | |
a7813a04 XL |
3393 | } |
3394 | ||
5bcae85e | 3395 | fn run_mir_opt_test(&self) { |
ba9703b0 XL |
3396 | let pm = self.pass_mode(); |
3397 | let should_run = self.should_run(pm); | |
3398 | let emit_metadata = self.should_emit_metadata(pm); | |
3399 | let proc_res = self.compile_test(should_run, emit_metadata); | |
5bcae85e SL |
3400 | |
3401 | if !proc_res.status.success() { | |
3402 | self.fatal_proc_rec("compilation failed!", &proc_res); | |
3403 | } | |
3404 | ||
ba9703b0 | 3405 | self.check_mir_dump(); |
5bcae85e | 3406 | |
ba9703b0 XL |
3407 | if let WillExecute::Yes = should_run { |
3408 | let proc_res = self.exec_compiled_test(); | |
3409 | ||
3410 | if !proc_res.status.success() { | |
3411 | self.fatal_proc_rec("test run failed!", &proc_res); | |
3412 | } | |
5bcae85e | 3413 | } |
5bcae85e SL |
3414 | } |
3415 | ||
3416 | fn check_mir_dump(&self) { | |
0731742a | 3417 | let test_file_contents = fs::read_to_string(&self.testpaths.file).unwrap(); |
ba9703b0 | 3418 | |
3dfed10e XL |
3419 | let test_dir = self.testpaths.file.parent().unwrap(); |
3420 | let test_crate = | |
3421 | self.testpaths.file.file_stem().unwrap().to_str().unwrap().replace("-", "_"); | |
ba9703b0 | 3422 | |
3dfed10e | 3423 | let mut bit_width = String::new(); |
ba9703b0 | 3424 | if test_file_contents.lines().any(|l| l == "// EMIT_MIR_FOR_EACH_BIT_WIDTH") { |
f2b60f7d | 3425 | bit_width = format!(".{}bit", self.config.get_pointer_width()); |
ba9703b0 XL |
3426 | } |
3427 | ||
3428 | if self.config.bless { | |
3dfed10e | 3429 | for e in |
1b1a35ee | 3430 | glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, bit_width)).unwrap() |
3dfed10e XL |
3431 | { |
3432 | std::fs::remove_file(e.unwrap()).unwrap(); | |
3433 | } | |
3434 | for e in | |
1b1a35ee | 3435 | glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, bit_width)).unwrap() |
3dfed10e XL |
3436 | { |
3437 | std::fs::remove_file(e.unwrap()).unwrap(); | |
3438 | } | |
ba9703b0 | 3439 | } |
3dfed10e | 3440 | |
487cf647 FG |
3441 | let files = miropt_test_tools::files_for_miropt_test( |
3442 | &self.testpaths.file, | |
3443 | self.config.get_pointer_width(), | |
3444 | ); | |
3445 | ||
3446 | for miropt_test_tools::MiroptTestFiles { from_file, to_file, expected_file } in files { | |
3447 | let dumped_string = if let Some(after) = to_file { | |
3448 | self.diff_mir_files(from_file.into(), after.into()) | |
3449 | } else { | |
3450 | let mut output_file = PathBuf::new(); | |
3451 | output_file.push(self.get_mir_dump_dir()); | |
3452 | output_file.push(&from_file); | |
3453 | debug!( | |
3454 | "comparing the contents of: {} with {}", | |
3455 | output_file.display(), | |
3456 | expected_file.display() | |
3457 | ); | |
3458 | if !output_file.exists() { | |
3459 | panic!( | |
3460 | "Output file `{}` from test does not exist, available files are in `{}`", | |
3461 | output_file.display(), | |
3462 | output_file.parent().unwrap().display() | |
3dfed10e | 3463 | ); |
3dfed10e | 3464 | } |
487cf647 FG |
3465 | self.check_mir_test_timestamp(&from_file, &output_file); |
3466 | let dumped_string = fs::read_to_string(&output_file).unwrap(); | |
3467 | self.normalize_output(&dumped_string, &[]) | |
3468 | }; | |
3dfed10e | 3469 | |
487cf647 FG |
3470 | if self.config.bless { |
3471 | let _ = std::fs::remove_file(&expected_file); | |
3472 | std::fs::write(expected_file, dumped_string.as_bytes()).unwrap(); | |
3473 | } else { | |
3474 | if !expected_file.exists() { | |
3475 | panic!("Output file `{}` from test does not exist", expected_file.display()); | |
3476 | } | |
3477 | let expected_string = fs::read_to_string(&expected_file).unwrap(); | |
3478 | if dumped_string != expected_string { | |
3479 | print!("{}", write_diff(&expected_string, &dumped_string, 3)); | |
3480 | panic!( | |
3481 | "Actual MIR output differs from expected MIR output {}", | |
ba9703b0 XL |
3482 | expected_file.display() |
3483 | ); | |
5bcae85e SL |
3484 | } |
3485 | } | |
3486 | } | |
3487 | } | |
3488 | ||
3dfed10e XL |
3489 | fn diff_mir_files(&self, before: PathBuf, after: PathBuf) -> String { |
3490 | let to_full_path = |path: PathBuf| { | |
3491 | let full = self.get_mir_dump_dir().join(&path); | |
3492 | if !full.exists() { | |
3493 | panic!( | |
3494 | "the mir dump file for {} does not exist (requested in {})", | |
3495 | path.display(), | |
3496 | self.testpaths.file.display(), | |
3497 | ); | |
3498 | } | |
3499 | full | |
3500 | }; | |
3501 | let before = to_full_path(before); | |
3502 | let after = to_full_path(after); | |
3503 | debug!("comparing the contents of: {} with {}", before.display(), after.display()); | |
3504 | let before = fs::read_to_string(before).unwrap(); | |
3505 | let after = fs::read_to_string(after).unwrap(); | |
3506 | let before = self.normalize_output(&before, &[]); | |
3507 | let after = self.normalize_output(&after, &[]); | |
3508 | let mut dumped_string = String::new(); | |
3509 | for result in diff::lines(&before, &after) { | |
3510 | use std::fmt::Write; | |
3511 | match result { | |
3512 | diff::Result::Left(s) => writeln!(dumped_string, "- {}", s).unwrap(), | |
3513 | diff::Result::Right(s) => writeln!(dumped_string, "+ {}", s).unwrap(), | |
3514 | diff::Result::Both(s, _) => writeln!(dumped_string, " {}", s).unwrap(), | |
3515 | } | |
3516 | } | |
3517 | dumped_string | |
3518 | } | |
3519 | ||
8bb4bdeb | 3520 | fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Path) { |
48663c56 | 3521 | let t = |file| fs::metadata(file).unwrap().modified().unwrap(); |
8bb4bdeb XL |
3522 | let source_file = &self.testpaths.file; |
3523 | let output_time = t(output_file); | |
3524 | let source_time = t(source_file); | |
3525 | if source_time > output_time { | |
dfeec247 | 3526 | debug!("source file time: {:?} output file time: {:?}", source_time, output_time); |
ff7c6d11 XL |
3527 | panic!( |
3528 | "test source file `{}` is newer than potentially stale output file `{}`.", | |
3529 | source_file.display(), | |
3530 | test_name | |
3531 | ); | |
8bb4bdeb XL |
3532 | } |
3533 | } | |
3534 | ||
5bcae85e | 3535 | fn get_mir_dump_dir(&self) -> PathBuf { |
abe05a73 | 3536 | let mut mir_dump_dir = PathBuf::from(self.config.build_base.as_path()); |
5bcae85e | 3537 | debug!("input_file: {:?}", self.testpaths.file); |
abe05a73 XL |
3538 | mir_dump_dir.push(&self.testpaths.relative_dir); |
3539 | mir_dump_dir.push(self.testpaths.file.file_stem().unwrap()); | |
5bcae85e SL |
3540 | mir_dump_dir |
3541 | } | |
3542 | ||
041b39d2 | 3543 | fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String { |
abe05a73 | 3544 | let cflags = self.props.compile_flags.join(" "); |
ff7c6d11 XL |
3545 | let json = cflags.contains("--error-format json") |
3546 | || cflags.contains("--error-format pretty-json") | |
3547 | || cflags.contains("--error-format=json") | |
ba9703b0 XL |
3548 | || cflags.contains("--error-format=pretty-json") |
3549 | || cflags.contains("--output-format json") | |
3550 | || cflags.contains("--output-format=json"); | |
48663c56 XL |
3551 | |
3552 | let mut normalized = output.to_string(); | |
3553 | ||
3554 | let mut normalize_path = |from: &Path, to: &str| { | |
3555 | let mut from = from.display().to_string(); | |
3556 | if json { | |
3557 | from = from.replace("\\", "\\\\"); | |
3558 | } | |
3559 | normalized = normalized.replace(&from, to); | |
abe05a73 XL |
3560 | }; |
3561 | ||
48663c56 XL |
3562 | let parent_dir = self.testpaths.file.parent().unwrap(); |
3563 | normalize_path(parent_dir, "$DIR"); | |
ff7c6d11 | 3564 | |
9c376795 FG |
3565 | if self.props.remap_src_base { |
3566 | let mut remapped_parent_dir = PathBuf::from(FAKE_SRC_BASE); | |
3567 | if self.testpaths.relative_dir != Path::new("") { | |
3568 | remapped_parent_dir.push(&self.testpaths.relative_dir); | |
3569 | } | |
3570 | normalize_path(&remapped_parent_dir, "$DIR"); | |
3571 | } | |
3572 | ||
487cf647 | 3573 | let source_bases = &[ |
9c376795 FG |
3574 | // Source base on the current filesystem (calculated as parent of `tests/$suite`): |
3575 | Some(self.config.src_base.parent().unwrap().parent().unwrap().into()), | |
487cf647 FG |
3576 | // Source base on the sysroot (from the src components downloaded by `download-rustc`): |
3577 | Some(self.config.sysroot_base.join("lib").join("rustlib").join("src").join("rust")), | |
3578 | // Virtual `/rustc/$sha` remapped paths (if `remap-debuginfo` is enabled): | |
3579 | option_env!("CFG_VIRTUAL_RUST_SOURCE_BASE_DIR").map(PathBuf::from), | |
3580 | // Virtual `/rustc/$sha` coming from download-rustc: | |
3581 | std::env::var_os("FAKE_DOWNLOAD_RUSTC_PREFIX").map(PathBuf::from), | |
3582 | // Tests using -Zsimulate-remapped-rust-src-base should use this fake path | |
3583 | Some("/rustc/FAKE_PREFIX".into()), | |
3584 | ]; | |
3585 | for base_dir in source_bases { | |
3586 | if let Some(base_dir) = base_dir { | |
3587 | // Paths into the libstd/libcore | |
3588 | normalize_path(&base_dir.join("library"), "$SRC_DIR"); | |
3589 | // `ui-fulldeps` tests can show paths to the compiler source when testing macros from | |
3590 | // `rustc_macros` | |
3591 | // eg. /home/user/rust/compiler | |
3592 | normalize_path(&base_dir.join("compiler"), "$COMPILER_DIR"); | |
3593 | } | |
17df50a5 XL |
3594 | } |
3595 | ||
b7449926 XL |
3596 | // Paths into the build directory |
3597 | let test_build_dir = &self.config.build_base; | |
3598 | let parent_build_dir = test_build_dir.parent().unwrap().parent().unwrap().parent().unwrap(); | |
3599 | ||
3600 | // eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui | |
48663c56 | 3601 | normalize_path(test_build_dir, "$TEST_BUILD_DIR"); |
b7449926 | 3602 | // eg. /home/user/rust/build |
48663c56 | 3603 | normalize_path(parent_build_dir, "$BUILD_DIR"); |
b7449926 XL |
3604 | |
3605 | // Paths into lib directory. | |
48663c56 | 3606 | normalize_path(&parent_build_dir.parent().unwrap().join("lib"), "$LIB_DIR"); |
b7449926 | 3607 | |
ff7c6d11 XL |
3608 | if json { |
3609 | // escaped newlines in json strings should be readable | |
3610 | // in the stderr files. There's no point int being correct, | |
3611 | // since only humans process the stderr files. | |
3612 | // Thus we just turn escaped newlines back into newlines. | |
3613 | normalized = normalized.replace("\\n", "\n"); | |
3614 | } | |
3615 | ||
b7449926 XL |
3616 | // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them |
3617 | // with placeholders as we do not want tests needing updated when compiler source code | |
3618 | // changes. | |
3619 | // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL | |
5099ac24 FG |
3620 | lazy_static! { |
3621 | static ref SRC_DIR_RE: Regex = | |
3622 | Regex::new("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?").unwrap(); | |
3623 | } | |
3624 | ||
3625 | normalized = SRC_DIR_RE.replace_all(&normalized, "SRC_DIR$1:LL:COL").into_owned(); | |
b7449926 | 3626 | |
532ac7d7 XL |
3627 | normalized = Self::normalize_platform_differences(&normalized); |
3628 | normalized = normalized.replace("\t", "\\t"); // makes tabs visible | |
3629 | ||
3630 | // Remove test annotations like `//~ ERROR text` from the output, | |
3631 | // since they duplicate actual errors and make the output hard to read. | |
cdc7bbd5 XL |
3632 | // This mirrors the regex in src/tools/tidy/src/style.rs, please update |
3633 | // both if either are changed. | |
5099ac24 FG |
3634 | lazy_static! { |
3635 | static ref ANNOTATION_RE: Regex = Regex::new("\\s*//(\\[.*\\])?~.*").unwrap(); | |
3636 | } | |
3637 | ||
3638 | normalized = ANNOTATION_RE.replace_all(&normalized, "").into_owned(); | |
3639 | ||
3640 | // This code normalizes various hashes in v0 symbol mangling that is | |
3641 | // emitted in the ui and mir-opt tests. | |
3642 | lazy_static! { | |
3643 | static ref V0_CRATE_HASH_PREFIX_RE: Regex = | |
3644 | Regex::new(r"_R.*?Cs[0-9a-zA-Z]+_").unwrap(); | |
3645 | static ref V0_CRATE_HASH_RE: Regex = Regex::new(r"Cs[0-9a-zA-Z]+_").unwrap(); | |
3646 | } | |
532ac7d7 | 3647 | |
a2a8927a | 3648 | const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_"; |
5099ac24 | 3649 | if V0_CRATE_HASH_PREFIX_RE.is_match(&normalized) { |
a2a8927a | 3650 | // Normalize crate hash |
5099ac24 FG |
3651 | normalized = |
3652 | V0_CRATE_HASH_RE.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned(); | |
3653 | } | |
3654 | ||
3655 | lazy_static! { | |
3656 | static ref V0_BACK_REF_PREFIX_RE: Regex = Regex::new(r"\(_R.*?B[0-9a-zA-Z]_").unwrap(); | |
3657 | static ref V0_BACK_REF_RE: Regex = Regex::new(r"B[0-9a-zA-Z]_").unwrap(); | |
a2a8927a | 3658 | } |
5099ac24 FG |
3659 | |
3660 | const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_"; | |
3661 | if V0_BACK_REF_PREFIX_RE.is_match(&normalized) { | |
a2a8927a | 3662 | // Normalize back references (see RFC 2603) |
5099ac24 FG |
3663 | normalized = |
3664 | V0_BACK_REF_RE.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned(); | |
a2a8927a | 3665 | } |
a2a8927a XL |
3666 | |
3667 | // Custom normalization rules | |
041b39d2 | 3668 | for rule in custom_rules { |
ff7c6d11 XL |
3669 | let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule"); |
3670 | normalized = re.replace_all(&normalized, &rule.1[..]).into_owned(); | |
041b39d2 XL |
3671 | } |
3672 | normalized | |
a7813a04 XL |
3673 | } |
3674 | ||
532ac7d7 XL |
3675 | /// Normalize output differences across platforms. Generally changes Windows output to be more |
3676 | /// Unix-like. | |
3677 | /// | |
3678 | /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings | |
3679 | /// with LF. | |
3680 | fn normalize_platform_differences(output: &str) -> String { | |
3681 | lazy_static! { | |
3682 | /// Used to find Windows paths. | |
3683 | /// | |
3684 | /// It's not possible to detect paths in the error messages generally, but this is a | |
3685 | /// decent enough heuristic. | |
3686 | static ref PATH_BACKSLASH_RE: Regex = Regex::new(r#"(?x) | |
3687 | (?: | |
3688 | # Match paths that don't include spaces. | |
3689 | (?:\\[\pL\pN\.\-_']+)+\.\pL+ | |
3690 | | | |
3691 | # If the path starts with a well-known root, then allow spaces. | |
3692 | \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_' ]+)+ | |
3693 | )"# | |
3694 | ).unwrap(); | |
3695 | } | |
3696 | ||
3697 | let output = output.replace(r"\\", r"\"); | |
3698 | ||
dfeec247 XL |
3699 | PATH_BACKSLASH_RE |
3700 | .replace_all(&output, |caps: &Captures<'_>| { | |
3701 | println!("{}", &caps[0]); | |
3702 | caps[0].replace(r"\", "/") | |
3703 | }) | |
3704 | .replace("\r\n", "\n") | |
532ac7d7 XL |
3705 | } |
3706 | ||
a7813a04 | 3707 | fn expected_output_path(&self, kind: &str) -> PathBuf { |
dfeec247 XL |
3708 | let mut path = |
3709 | expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind); | |
94b46f34 XL |
3710 | |
3711 | if !path.exists() { | |
3712 | if let Some(CompareMode::Polonius) = self.config.compare_mode { | |
923072b8 | 3713 | path = expected_output_path(&self.testpaths, self.revision, &None, kind); |
94b46f34 XL |
3714 | } |
3715 | } | |
3716 | ||
3717 | if !path.exists() { | |
83c7162d XL |
3718 | path = expected_output_path(&self.testpaths, self.revision, &None, kind); |
3719 | } | |
3720 | ||
3721 | path | |
a7813a04 XL |
3722 | } |
3723 | ||
83c7162d XL |
3724 | fn load_expected_output(&self, kind: &str) -> String { |
3725 | let path = self.expected_output_path(kind); | |
3726 | if path.exists() { | |
3727 | match self.load_expected_output_from_path(&path) { | |
3728 | Ok(x) => x, | |
3729 | Err(x) => self.fatal(&x), | |
3730 | } | |
3731 | } else { | |
3732 | String::new() | |
a7813a04 | 3733 | } |
83c7162d | 3734 | } |
a7813a04 | 3735 | |
83c7162d | 3736 | fn load_expected_output_from_path(&self, path: &Path) -> Result<String, String> { |
0731742a XL |
3737 | fs::read_to_string(path).map_err(|err| { |
3738 | format!("failed to load expected output from `{}`: {}", path.display(), err) | |
3739 | }) | |
a7813a04 XL |
3740 | } |
3741 | ||
94b46f34 | 3742 | fn delete_file(&self, file: &PathBuf) { |
f9f354fc | 3743 | if !file.exists() { |
f2b60f7d | 3744 | // Deleting a nonexistent file would error. |
f9f354fc XL |
3745 | return; |
3746 | } | |
0731742a | 3747 | if let Err(e) = fs::remove_file(file) { |
dfeec247 | 3748 | self.fatal(&format!("failed to delete `{}`: {}", file.display(), e,)); |
94b46f34 XL |
3749 | } |
3750 | } | |
3751 | ||
a7813a04 XL |
3752 | fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize { |
3753 | if actual == expected { | |
3754 | return 0; | |
3755 | } | |
3756 | ||
94b46f34 XL |
3757 | if !self.config.bless { |
3758 | if expected.is_empty() { | |
3759 | println!("normalized {}:\n{}\n", kind, actual); | |
3760 | } else { | |
3761 | println!("diff of {}:\n", kind); | |
3dfed10e | 3762 | print!("{}", write_diff(expected, actual, 3)); |
7cac9316 | 3763 | } |
a7813a04 XL |
3764 | } |
3765 | ||
83c7162d | 3766 | let mode = self.config.compare_mode.as_ref().map_or("", |m| m.to_str()); |
94b46f34 XL |
3767 | let output_file = self |
3768 | .output_base_name() | |
83c7162d XL |
3769 | .with_extra_extension(self.revision.unwrap_or("")) |
3770 | .with_extra_extension(mode) | |
3771 | .with_extra_extension(kind); | |
3772 | ||
94b46f34 XL |
3773 | let mut files = vec![output_file]; |
3774 | if self.config.bless { | |
6a06907d XL |
3775 | // Delete non-revision .stderr/.stdout file if revisions are used. |
3776 | // Without this, we'd just generate the new files and leave the old files around. | |
3777 | if self.revision.is_some() { | |
3778 | let old = | |
3779 | expected_output_path(self.testpaths, None, &self.config.compare_mode, kind); | |
3780 | self.delete_file(&old); | |
3781 | } | |
94b46f34 XL |
3782 | files.push(expected_output_path( |
3783 | self.testpaths, | |
3784 | self.revision, | |
3785 | &self.config.compare_mode, | |
ff7c6d11 | 3786 | kind, |
94b46f34 XL |
3787 | )); |
3788 | } | |
3789 | ||
3790 | for output_file in &files { | |
3791 | if actual.is_empty() { | |
3792 | self.delete_file(output_file); | |
0731742a XL |
3793 | } else if let Err(err) = fs::write(&output_file, &actual) { |
3794 | self.fatal(&format!( | |
3795 | "failed to write {} to `{}`: {}", | |
3796 | kind, | |
3797 | output_file.display(), | |
3798 | err, | |
3799 | )); | |
94b46f34 | 3800 | } |
a7813a04 XL |
3801 | } |
3802 | ||
3803 | println!("\nThe actual {0} differed from the expected {0}.", kind); | |
94b46f34 XL |
3804 | for output_file in files { |
3805 | println!("Actual {} saved to {}", kind, output_file.display()); | |
3806 | } | |
dfeec247 | 3807 | if self.config.bless { 0 } else { 1 } |
94b46f34 XL |
3808 | } |
3809 | ||
923072b8 FG |
3810 | fn check_and_prune_duplicate_outputs( |
3811 | &self, | |
3812 | proc_res: &ProcRes, | |
3813 | modes: &[CompareMode], | |
3814 | require_same_modes: &[CompareMode], | |
3815 | ) { | |
3816 | for kind in UI_EXTENSIONS { | |
3817 | let canon_comparison_path = | |
3818 | expected_output_path(&self.testpaths, self.revision, &None, kind); | |
94b46f34 | 3819 | |
923072b8 FG |
3820 | let canon = match self.load_expected_output_from_path(&canon_comparison_path) { |
3821 | Ok(canon) => canon, | |
3822 | _ => continue, | |
3823 | }; | |
3824 | let bless = self.config.bless; | |
3825 | let check_and_prune_duplicate_outputs = |mode: &CompareMode, require_same: bool| { | |
3826 | let examined_path = | |
3827 | expected_output_path(&self.testpaths, self.revision, &Some(mode.clone()), kind); | |
3828 | ||
3829 | // If there is no output, there is nothing to do | |
3830 | let examined_content = match self.load_expected_output_from_path(&examined_path) { | |
3831 | Ok(content) => content, | |
3832 | _ => return, | |
3833 | }; | |
94b46f34 | 3834 | |
923072b8 | 3835 | let is_duplicate = canon == examined_content; |
94b46f34 | 3836 | |
923072b8 FG |
3837 | match (bless, require_same, is_duplicate) { |
3838 | // If we're blessing and the output is the same, then delete the file. | |
3839 | (true, _, true) => { | |
3840 | self.delete_file(&examined_path); | |
94b46f34 | 3841 | } |
923072b8 FG |
3842 | // If we want them to be the same, but they are different, then error. |
3843 | // We do this wether we bless or not | |
3844 | (_, true, false) => { | |
3845 | self.fatal_proc_rec( | |
3846 | &format!("`{}` should not have different output from base test!", kind), | |
3847 | proc_res, | |
3848 | ); | |
3849 | } | |
3850 | _ => {} | |
94b46f34 | 3851 | } |
923072b8 FG |
3852 | }; |
3853 | for mode in modes { | |
3854 | check_and_prune_duplicate_outputs(mode, false); | |
3855 | } | |
3856 | for mode in require_same_modes { | |
3857 | check_and_prune_duplicate_outputs(mode, true); | |
94b46f34 XL |
3858 | } |
3859 | } | |
3860 | } | |
3861 | ||
3862 | fn create_stamp(&self) { | |
9fa01778 | 3863 | let stamp = crate::stamp(&self.config, self.testpaths, self.revision); |
0731742a | 3864 | fs::write(&stamp, compute_stamp_hash(&self.config)).unwrap(); |
a7813a04 XL |
3865 | } |
3866 | } | |
3867 | ||
3868 | struct ProcArgs { | |
3869 | prog: String, | |
3870 | args: Vec<String>, | |
3871 | } | |
3872 | ||
3873 | pub struct ProcRes { | |
7cac9316 | 3874 | status: ExitStatus, |
a7813a04 XL |
3875 | stdout: String, |
3876 | stderr: String, | |
3877 | cmdline: String, | |
3878 | } | |
3879 | ||
a7813a04 | 3880 | impl ProcRes { |
5099ac24 | 3881 | pub fn print_info(&self) { |
5e7ed085 FG |
3882 | fn render(name: &str, contents: &str) -> String { |
3883 | let contents = json::extract_rendered(contents); | |
3884 | let contents = contents.trim(); | |
3885 | if contents.is_empty() { | |
3886 | format!("{name}: none") | |
3887 | } else { | |
3888 | format!( | |
3889 | "\ | |
3890 | --- {name} -------------------------------\n\ | |
3891 | {contents}\n\ | |
3892 | ------------------------------------------", | |
3893 | ) | |
3894 | } | |
3895 | } | |
3896 | ||
3897 | println!( | |
3898 | "status: {}\ncommand: {}\n{}\n{}\n", | |
dfeec247 XL |
3899 | self.status, |
3900 | self.cmdline, | |
5e7ed085 FG |
3901 | render("stdout", &self.stdout), |
3902 | render("stderr", &self.stderr), | |
ff7c6d11 | 3903 | ); |
5099ac24 FG |
3904 | } |
3905 | ||
3906 | pub fn fatal(&self, err: Option<&str>, on_failure: impl FnOnce()) -> ! { | |
3907 | if let Some(e) = err { | |
3908 | println!("\nerror: {}", e); | |
3909 | } | |
3910 | self.print_info(); | |
fc512014 | 3911 | on_failure(); |
48663c56 XL |
3912 | // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from |
3913 | // compiletest, which is unnecessary noise. | |
3914 | std::panic::resume_unwind(Box::new(())); | |
a7813a04 XL |
3915 | } |
3916 | } | |
3917 | ||
3dfed10e | 3918 | #[derive(Debug)] |
a7813a04 XL |
3919 | enum TargetLocation { |
3920 | ThisFile(PathBuf), | |
3921 | ThisDirectory(PathBuf), | |
3922 | } | |
3923 | ||
74b04a01 XL |
3924 | enum AllowUnused { |
3925 | Yes, | |
3926 | No, | |
3927 | } | |
487cf647 FG |
3928 | |
3929 | enum LinkToAux { | |
3930 | Yes, | |
3931 | No, | |
3932 | } |