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