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