]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/header.rs
Auto merge of #61008 - GuillaumeGomez:fix-rustdoc-code-highlighting, r=Manishearth
[rust.git] / src / tools / compiletest / src / header.rs
1 use std::env;
2 use std::fs::File;
3 use std::io::prelude::*;
4 use std::io::BufReader;
5 use std::path::{Path, PathBuf};
6
7 use log::*;
8
9 use crate::common::{self, CompareMode, Config, Mode};
10 use crate::util;
11
12 use crate::extract_gdb_version;
13
14 /// Whether to ignore the test.
15 #[derive(Clone, Copy, PartialEq, Debug)]
16 pub enum Ignore {
17     /// Runs it.
18     Run,
19     /// Ignore it totally.
20     Ignore,
21     /// Ignore only the gdb test, but run the lldb test.
22     IgnoreGdb,
23     /// Ignore only the lldb test, but run the gdb test.
24     IgnoreLldb,
25 }
26
27 impl Ignore {
28     pub fn can_run_gdb(&self) -> bool {
29         *self == Ignore::Run || *self == Ignore::IgnoreLldb
30     }
31
32     pub fn can_run_lldb(&self) -> bool {
33         *self == Ignore::Run || *self == Ignore::IgnoreGdb
34     }
35
36     pub fn no_gdb(&self) -> Ignore {
37         match *self {
38             Ignore::Run => Ignore::IgnoreGdb,
39             Ignore::IgnoreGdb => Ignore::IgnoreGdb,
40             _ => Ignore::Ignore,
41         }
42     }
43
44     pub fn no_lldb(&self) -> Ignore {
45         match *self {
46             Ignore::Run => Ignore::IgnoreLldb,
47             Ignore::IgnoreLldb => Ignore::IgnoreLldb,
48             _ => Ignore::Ignore,
49         }
50     }
51 }
52
53 /// The result of parse_cfg_name_directive.
54 #[derive(Clone, Copy, PartialEq, Debug)]
55 enum ParsedNameDirective {
56     /// No match.
57     NoMatch,
58     /// Match.
59     Match,
60     /// Mode was DebugInfoGdbLldb and this matched gdb.
61     MatchGdb,
62     /// Mode was DebugInfoGdbLldb and this matched lldb.
63     MatchLldb,
64 }
65
66 /// Properties which must be known very early, before actually running
67 /// the test.
68 pub struct EarlyProps {
69     pub ignore: Ignore,
70     pub should_fail: bool,
71     pub aux: Vec<String>,
72     pub revisions: Vec<String>,
73 }
74
75 impl EarlyProps {
76     pub fn from_file(config: &Config, testfile: &Path) -> Self {
77         let mut props = EarlyProps {
78             ignore: Ignore::Run,
79             should_fail: false,
80             aux: Vec::new(),
81             revisions: vec![],
82         };
83
84         if config.mode == common::DebugInfoGdbLldb {
85             if config.lldb_python_dir.is_none() {
86                 props.ignore = props.ignore.no_lldb();
87             }
88             if config.gdb_version.is_none() {
89                 props.ignore = props.ignore.no_gdb();
90             }
91         } else if config.mode == common::DebugInfoCdb {
92             if config.cdb.is_none() {
93                 props.ignore = Ignore::Ignore;
94             }
95         }
96
97         let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some();
98         let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some();
99
100         iter_header(testfile, None, &mut |ln| {
101             // we should check if any only-<platform> exists and if it exists
102             // and does not matches the current platform, skip the test
103             if props.ignore != Ignore::Ignore {
104                 props.ignore = match config.parse_cfg_name_directive(ln, "ignore") {
105                     ParsedNameDirective::Match => Ignore::Ignore,
106                     ParsedNameDirective::NoMatch => props.ignore,
107                     ParsedNameDirective::MatchGdb => props.ignore.no_gdb(),
108                     ParsedNameDirective::MatchLldb => props.ignore.no_lldb(),
109                 };
110
111                 if config.has_cfg_prefix(ln, "only") {
112                     props.ignore = match config.parse_cfg_name_directive(ln, "only") {
113                         ParsedNameDirective::Match => props.ignore,
114                         ParsedNameDirective::NoMatch => Ignore::Ignore,
115                         ParsedNameDirective::MatchLldb => props.ignore.no_gdb(),
116                         ParsedNameDirective::MatchGdb => props.ignore.no_lldb(),
117                     };
118                 }
119
120                 if ignore_llvm(config, ln) {
121                     props.ignore = Ignore::Ignore;
122                 }
123
124                 if config.run_clang_based_tests_with.is_none() &&
125                    config.parse_needs_matching_clang(ln) {
126                     props.ignore = Ignore::Ignore;
127                 }
128
129                 if !rustc_has_profiler_support &&
130                    config.parse_needs_profiler_support(ln) {
131                     props.ignore = Ignore::Ignore;
132                 }
133
134                 if !rustc_has_sanitizer_support &&
135                    config.parse_needs_sanitizer_support(ln) {
136                     props.ignore = Ignore::Ignore;
137                 }
138             }
139
140             if (config.mode == common::DebugInfoGdb || config.mode == common::DebugInfoGdbLldb) &&
141                 props.ignore.can_run_gdb() && ignore_gdb(config, ln) {
142                 props.ignore = props.ignore.no_gdb();
143             }
144
145             if (config.mode == common::DebugInfoLldb || config.mode == common::DebugInfoGdbLldb) &&
146                 props.ignore.can_run_lldb() && ignore_lldb(config, ln) {
147                 props.ignore = props.ignore.no_lldb();
148             }
149
150             if let Some(s) = config.parse_aux_build(ln) {
151                 props.aux.push(s);
152             }
153
154             if let Some(r) = config.parse_revisions(ln) {
155                 props.revisions.extend(r);
156             }
157
158             props.should_fail = props.should_fail || config.parse_name_directive(ln, "should-fail");
159         });
160
161         return props;
162
163         fn ignore_gdb(config: &Config, line: &str) -> bool {
164             if let Some(actual_version) = config.gdb_version {
165                 if line.starts_with("min-gdb-version") {
166                     let (start_ver, end_ver) = extract_gdb_version_range(line);
167
168                     if start_ver != end_ver {
169                         panic!("Expected single GDB version")
170                     }
171                     // Ignore if actual version is smaller the minimum required
172                     // version
173                     actual_version < start_ver
174                 } else if line.starts_with("ignore-gdb-version") {
175                     let (min_version, max_version) = extract_gdb_version_range(line);
176
177                     if max_version < min_version {
178                         panic!("Malformed GDB version range: max < min")
179                     }
180
181                     actual_version >= min_version && actual_version <= max_version
182                 } else {
183                     false
184                 }
185             } else {
186                 false
187             }
188         }
189
190         // Takes a directive of the form "ignore-gdb-version <version1> [- <version2>]",
191         // returns the numeric representation of <version1> and <version2> as
192         // tuple: (<version1> as u32, <version2> as u32)
193         // If the <version2> part is omitted, the second component of the tuple
194         // is the same as <version1>.
195         fn extract_gdb_version_range(line: &str) -> (u32, u32) {
196             const ERROR_MESSAGE: &'static str = "Malformed GDB version directive";
197
198             let range_components = line.split(&[' ', '-'][..])
199                                        .filter(|word| !word.is_empty())
200                                        .map(extract_gdb_version)
201                                        .skip_while(Option::is_none)
202                                        .take(3) // 3 or more = invalid, so take at most 3.
203                                        .collect::<Vec<Option<u32>>>();
204
205             match range_components.len() {
206                 1 => {
207                     let v = range_components[0].unwrap();
208                     (v, v)
209                 }
210                 2 => {
211                     let v_min = range_components[0].unwrap();
212                     let v_max = range_components[1].expect(ERROR_MESSAGE);
213                     (v_min, v_max)
214                 }
215                 _ => panic!(ERROR_MESSAGE),
216             }
217         }
218
219         fn ignore_lldb(config: &Config, line: &str) -> bool {
220             if let Some(ref actual_version) = config.lldb_version {
221                 if line.starts_with("min-lldb-version") {
222                     let min_version = line.trim_end()
223                         .rsplit(' ')
224                         .next()
225                         .expect("Malformed lldb version directive");
226                     // Ignore if actual version is smaller the minimum required
227                     // version
228                     lldb_version_to_int(actual_version) < lldb_version_to_int(min_version)
229                 } else if line.starts_with("rust-lldb") && !config.lldb_native_rust {
230                     true
231                 } else {
232                     false
233                 }
234             } else {
235                 false
236             }
237         }
238
239         fn ignore_llvm(config: &Config, line: &str) -> bool {
240             if config.system_llvm && line.starts_with("no-system-llvm") {
241                 return true;
242             }
243             if let Some(ref actual_version) = config.llvm_version {
244                 if line.starts_with("min-llvm-version") {
245                     let min_version = line.trim_end()
246                         .rsplit(' ')
247                         .next()
248                         .expect("Malformed llvm version directive");
249                     // Ignore if actual version is smaller the minimum required
250                     // version
251                     &actual_version[..] < min_version
252                 } else if line.starts_with("min-system-llvm-version") {
253                     let min_version = line.trim_end()
254                         .rsplit(' ')
255                         .next()
256                         .expect("Malformed llvm version directive");
257                     // Ignore if using system LLVM and actual version
258                     // is smaller the minimum required version
259                     config.system_llvm && &actual_version[..] < min_version
260                 } else if line.starts_with("ignore-llvm-version") {
261                     // Syntax is: "ignore-llvm-version <version1> [- <version2>]"
262                     let range_components = line.split(' ')
263                         .skip(1) // Skip the directive.
264                         .map(|s| s.trim())
265                         .filter(|word| !word.is_empty() && word != &"-")
266                         .take(3) // 3 or more = invalid, so take at most 3.
267                         .collect::<Vec<&str>>();
268                     match range_components.len() {
269                         1 => {
270                             &actual_version[..] == range_components[0]
271                         }
272                         2 => {
273                             let v_min = range_components[0];
274                             let v_max = range_components[1];
275                             if v_max < v_min {
276                                 panic!("Malformed LLVM version range: max < min")
277                             }
278                             // Ignore if version lies inside of range.
279                             &actual_version[..] >= v_min && &actual_version[..] <= v_max
280                         }
281                         _ => panic!("Malformed LLVM version directive"),
282                     }
283                 } else {
284                     false
285                 }
286             } else {
287                 false
288             }
289         }
290     }
291 }
292
293 #[derive(Clone, Debug)]
294 pub struct TestProps {
295     // Lines that should be expected, in order, on standard out
296     pub error_patterns: Vec<String>,
297     // Extra flags to pass to the compiler
298     pub compile_flags: Vec<String>,
299     // Extra flags to pass when the compiled code is run (such as --bench)
300     pub run_flags: Option<String>,
301     // If present, the name of a file that this test should match when
302     // pretty-printed
303     pub pp_exact: Option<PathBuf>,
304     // Other crates that should be compiled (typically from the same
305     // directory as the test, but for backwards compatibility reasons
306     // we also check the auxiliary directory)
307     pub aux_builds: Vec<String>,
308     // A list of crates to pass '--extern-private name:PATH' flags for
309     // This should be a subset of 'aux_build'
310     // FIXME: Replace this with a better solution: https://github.com/rust-lang/rust/pull/54020
311     pub extern_private: Vec<String>,
312     // Environment settings to use for compiling
313     pub rustc_env: Vec<(String, String)>,
314     // Environment variables to unset prior to compiling.
315     // Variables are unset before applying 'rustc_env'.
316     pub unset_rustc_env: Vec<String>,
317     // Environment settings to use during execution
318     pub exec_env: Vec<(String, String)>,
319     // Lines to check if they appear in the expected debugger output
320     pub check_lines: Vec<String>,
321     // Build documentation for all specified aux-builds as well
322     pub build_aux_docs: bool,
323     // Flag to force a crate to be built with the host architecture
324     pub force_host: bool,
325     // Check stdout for error-pattern output as well as stderr
326     pub check_stdout: bool,
327     // For UI tests, allows compiler to generate arbitrary output to stdout
328     pub dont_check_compiler_stdout: bool,
329     // For UI tests, allows compiler to generate arbitrary output to stderr
330     pub dont_check_compiler_stderr: bool,
331     // Don't force a --crate-type=dylib flag on the command line
332     //
333     // Set this for example if you have an auxiliary test file that contains
334     // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures
335     // that the aux file is compiled as a `proc-macro` and not as a `dylib`.
336     pub no_prefer_dynamic: bool,
337     // Run --pretty expanded when running pretty printing tests
338     pub pretty_expanded: bool,
339     // Which pretty mode are we testing with, default to 'normal'
340     pub pretty_mode: String,
341     // Only compare pretty output and don't try compiling
342     pub pretty_compare_only: bool,
343     // Patterns which must not appear in the output of a cfail test.
344     pub forbid_output: Vec<String>,
345     // Revisions to test for incremental compilation.
346     pub revisions: Vec<String>,
347     // Directory (if any) to use for incremental compilation.  This is
348     // not set by end-users; rather it is set by the incremental
349     // testing harness and used when generating compilation
350     // arguments. (In particular, it propagates to the aux-builds.)
351     pub incremental_dir: Option<PathBuf>,
352     // Specifies that a test must actually compile without errors.
353     pub compile_pass: bool,
354     // rustdoc will test the output of the `--test` option
355     pub check_test_line_numbers_match: bool,
356     // The test must be compiled and run successfully. Only used in UI tests for now.
357     pub run_pass: bool,
358     // Skip any codegen step and running the executable. Only for run-pass.
359     pub skip_codegen: bool,
360     // Do not pass `-Z ui-testing` to UI tests
361     pub disable_ui_testing_normalization: bool,
362     // customized normalization rules
363     pub normalize_stdout: Vec<(String, String)>,
364     pub normalize_stderr: Vec<(String, String)>,
365     pub failure_status: i32,
366     // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the
367     // resulting Rust code.
368     pub run_rustfix: bool,
369     // If true, `rustfix` will only apply `MachineApplicable` suggestions.
370     pub rustfix_only_machine_applicable: bool,
371     pub assembly_output: Option<String>,
372 }
373
374 impl TestProps {
375     pub fn new() -> Self {
376         TestProps {
377             error_patterns: vec![],
378             compile_flags: vec![],
379             run_flags: None,
380             pp_exact: None,
381             aux_builds: vec![],
382             extern_private: vec![],
383             revisions: vec![],
384             rustc_env: vec![],
385             unset_rustc_env: vec![],
386             exec_env: vec![],
387             check_lines: vec![],
388             build_aux_docs: false,
389             force_host: false,
390             check_stdout: false,
391             dont_check_compiler_stdout: false,
392             dont_check_compiler_stderr: false,
393             no_prefer_dynamic: false,
394             pretty_expanded: false,
395             pretty_mode: "normal".to_string(),
396             pretty_compare_only: false,
397             forbid_output: vec![],
398             incremental_dir: None,
399             compile_pass: false,
400             check_test_line_numbers_match: false,
401             run_pass: false,
402             skip_codegen: false,
403             disable_ui_testing_normalization: false,
404             normalize_stdout: vec![],
405             normalize_stderr: vec![],
406             failure_status: -1,
407             run_rustfix: false,
408             rustfix_only_machine_applicable: false,
409             assembly_output: None,
410         }
411     }
412
413     pub fn from_aux_file(&self, testfile: &Path, cfg: Option<&str>, config: &Config) -> Self {
414         let mut props = TestProps::new();
415
416         // copy over select properties to the aux build:
417         props.incremental_dir = self.incremental_dir.clone();
418         props.load_from(testfile, cfg, config);
419
420         props
421     }
422
423     pub fn from_file(testfile: &Path, cfg: Option<&str>, config: &Config) -> Self {
424         let mut props = TestProps::new();
425         props.load_from(testfile, cfg, config);
426         props
427     }
428
429     /// Loads properties from `testfile` into `props`. If a property is
430     /// tied to a particular revision `foo` (indicated by writing
431     /// `//[foo]`), then the property is ignored unless `cfg` is
432     /// `Some("foo")`.
433     fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) {
434         iter_header(testfile, cfg, &mut |ln| {
435             if let Some(ep) = config.parse_error_pattern(ln) {
436                 self.error_patterns.push(ep);
437             }
438
439             if let Some(flags) = config.parse_compile_flags(ln) {
440                 self.compile_flags
441                     .extend(flags.split_whitespace().map(|s| s.to_owned()));
442             }
443
444             if let Some(edition) = config.parse_edition(ln) {
445                 self.compile_flags.push(format!("--edition={}", edition));
446             }
447
448             if let Some(r) = config.parse_revisions(ln) {
449                 self.revisions.extend(r);
450             }
451
452             if self.run_flags.is_none() {
453                 self.run_flags = config.parse_run_flags(ln);
454             }
455
456             if self.pp_exact.is_none() {
457                 self.pp_exact = config.parse_pp_exact(ln, testfile);
458             }
459
460             if !self.build_aux_docs {
461                 self.build_aux_docs = config.parse_build_aux_docs(ln);
462             }
463
464             if !self.force_host {
465                 self.force_host = config.parse_force_host(ln);
466             }
467
468             if !self.check_stdout {
469                 self.check_stdout = config.parse_check_stdout(ln);
470             }
471
472             if !self.dont_check_compiler_stdout {
473                 self.dont_check_compiler_stdout = config.parse_dont_check_compiler_stdout(ln);
474             }
475
476             if !self.dont_check_compiler_stderr {
477                 self.dont_check_compiler_stderr = config.parse_dont_check_compiler_stderr(ln);
478             }
479
480             if !self.no_prefer_dynamic {
481                 self.no_prefer_dynamic = config.parse_no_prefer_dynamic(ln);
482             }
483
484             if !self.pretty_expanded {
485                 self.pretty_expanded = config.parse_pretty_expanded(ln);
486             }
487
488             if let Some(m) = config.parse_pretty_mode(ln) {
489                 self.pretty_mode = m;
490             }
491
492             if !self.pretty_compare_only {
493                 self.pretty_compare_only = config.parse_pretty_compare_only(ln);
494             }
495
496             if let Some(ab) = config.parse_aux_build(ln) {
497                 self.aux_builds.push(ab);
498             }
499
500             if let Some(ep) = config.parse_extern_private(ln) {
501                 self.extern_private.push(ep);
502             }
503
504             if let Some(ee) = config.parse_env(ln, "exec-env") {
505                 self.exec_env.push(ee);
506             }
507
508             if let Some(ee) = config.parse_env(ln, "rustc-env") {
509                 self.rustc_env.push(ee);
510             }
511
512             if let Some(ev) = config.parse_name_value_directive(ln, "unset-rustc-env") {
513                 self.unset_rustc_env.push(ev);
514             }
515
516             if let Some(cl) = config.parse_check_line(ln) {
517                 self.check_lines.push(cl);
518             }
519
520             if let Some(of) = config.parse_forbid_output(ln) {
521                 self.forbid_output.push(of);
522             }
523
524             if !self.check_test_line_numbers_match {
525                 self.check_test_line_numbers_match = config.parse_check_test_line_numbers_match(ln);
526             }
527
528             if !self.run_pass {
529                 self.run_pass = config.parse_run_pass(ln);
530             }
531
532             if !self.compile_pass {
533                 // run-pass implies compile_pass
534                 self.compile_pass = config.parse_compile_pass(ln) || self.run_pass;
535             }
536
537             if !self.skip_codegen {
538                 self.skip_codegen = config.parse_skip_codegen(ln);
539             }
540
541             if !self.disable_ui_testing_normalization {
542                 self.disable_ui_testing_normalization =
543                     config.parse_disable_ui_testing_normalization(ln);
544             }
545
546             if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stdout") {
547                 self.normalize_stdout.push(rule);
548             }
549             if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stderr") {
550                 self.normalize_stderr.push(rule);
551             }
552
553             if let Some(code) = config.parse_failure_status(ln) {
554                 self.failure_status = code;
555             }
556
557             if !self.run_rustfix {
558                 self.run_rustfix = config.parse_run_rustfix(ln);
559             }
560
561             if !self.rustfix_only_machine_applicable {
562                 self.rustfix_only_machine_applicable =
563                     config.parse_rustfix_only_machine_applicable(ln);
564             }
565
566             if self.assembly_output.is_none() {
567                 self.assembly_output = config.parse_assembly_output(ln);
568             }
569         });
570
571         if self.failure_status == -1 {
572             self.failure_status = match config.mode {
573                 Mode::RunFail => 101,
574                 _ => 1,
575             };
576         }
577
578         for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
579             if let Ok(val) = env::var(key) {
580                 if self.exec_env.iter().find(|&&(ref x, _)| x == key).is_none() {
581                     self.exec_env.push(((*key).to_owned(), val))
582                 }
583             }
584         }
585     }
586 }
587
588 fn iter_header(testfile: &Path, cfg: Option<&str>, it: &mut dyn FnMut(&str)) {
589     if testfile.is_dir() {
590         return;
591     }
592
593     let comment = if testfile.to_string_lossy().ends_with(".rs") {
594         "//"
595     } else {
596         "#"
597     };
598
599     // FIXME: would be nice to allow some whitespace between comment and brace :)
600     // It took me like 2 days to debug why compile-flags weren’t taken into account for my test :)
601     let comment_with_brace = comment.to_string() + "[";
602
603     let rdr = BufReader::new(File::open(testfile).unwrap());
604     for ln in rdr.lines() {
605         // Assume that any directives will be found before the first
606         // module or function. This doesn't seem to be an optimization
607         // with a warm page cache. Maybe with a cold one.
608         let ln = ln.unwrap();
609         let ln = ln.trim();
610         if ln.starts_with("fn") || ln.starts_with("mod") {
611             return;
612         } else if ln.starts_with(&comment_with_brace) {
613             // A comment like `//[foo]` is specific to revision `foo`
614             if let Some(close_brace) = ln.find(']') {
615                 let open_brace = ln.find('[').unwrap();
616                 let lncfg = &ln[open_brace + 1 .. close_brace];
617                 let matches = match cfg {
618                     Some(s) => s == &lncfg[..],
619                     None => false,
620                 };
621                 if matches {
622                     it(ln[(close_brace + 1)..].trim_start());
623                 }
624             } else {
625                 panic!("malformed condition directive: expected `{}foo]`, found `{}`",
626                         comment_with_brace, ln)
627             }
628         } else if ln.starts_with(comment) {
629             it(ln[comment.len() ..].trim_start());
630         }
631     }
632     return;
633 }
634
635 impl Config {
636     fn parse_error_pattern(&self, line: &str) -> Option<String> {
637         self.parse_name_value_directive(line, "error-pattern")
638     }
639
640     fn parse_forbid_output(&self, line: &str) -> Option<String> {
641         self.parse_name_value_directive(line, "forbid-output")
642     }
643
644     fn parse_aux_build(&self, line: &str) -> Option<String> {
645         self.parse_name_value_directive(line, "aux-build")
646             .map(|r| r.trim().to_string())
647     }
648
649     fn parse_extern_private(&self, line: &str) -> Option<String> {
650         self.parse_name_value_directive(line, "extern-private")
651     }
652
653     fn parse_compile_flags(&self, line: &str) -> Option<String> {
654         self.parse_name_value_directive(line, "compile-flags")
655     }
656
657     fn parse_revisions(&self, line: &str) -> Option<Vec<String>> {
658         self.parse_name_value_directive(line, "revisions")
659             .map(|r| r.split_whitespace().map(|t| t.to_string()).collect())
660     }
661
662     fn parse_run_flags(&self, line: &str) -> Option<String> {
663         self.parse_name_value_directive(line, "run-flags")
664     }
665
666     fn parse_check_line(&self, line: &str) -> Option<String> {
667         self.parse_name_value_directive(line, "check")
668     }
669
670     fn parse_force_host(&self, line: &str) -> bool {
671         self.parse_name_directive(line, "force-host")
672     }
673
674     fn parse_build_aux_docs(&self, line: &str) -> bool {
675         self.parse_name_directive(line, "build-aux-docs")
676     }
677
678     fn parse_check_stdout(&self, line: &str) -> bool {
679         self.parse_name_directive(line, "check-stdout")
680     }
681
682     fn parse_dont_check_compiler_stdout(&self, line: &str) -> bool {
683         self.parse_name_directive(line, "dont-check-compiler-stdout")
684     }
685
686     fn parse_dont_check_compiler_stderr(&self, line: &str) -> bool {
687         self.parse_name_directive(line, "dont-check-compiler-stderr")
688     }
689
690     fn parse_no_prefer_dynamic(&self, line: &str) -> bool {
691         self.parse_name_directive(line, "no-prefer-dynamic")
692     }
693
694     fn parse_pretty_expanded(&self, line: &str) -> bool {
695         self.parse_name_directive(line, "pretty-expanded")
696     }
697
698     fn parse_pretty_mode(&self, line: &str) -> Option<String> {
699         self.parse_name_value_directive(line, "pretty-mode")
700     }
701
702     fn parse_pretty_compare_only(&self, line: &str) -> bool {
703         self.parse_name_directive(line, "pretty-compare-only")
704     }
705
706     fn parse_failure_status(&self, line: &str) -> Option<i32> {
707         match self.parse_name_value_directive(line, "failure-status") {
708             Some(code) => code.trim().parse::<i32>().ok(),
709             _ => None,
710         }
711     }
712
713     fn parse_compile_pass(&self, line: &str) -> bool {
714         self.parse_name_directive(line, "compile-pass")
715     }
716
717     fn parse_disable_ui_testing_normalization(&self, line: &str) -> bool {
718         self.parse_name_directive(line, "disable-ui-testing-normalization")
719     }
720
721     fn parse_check_test_line_numbers_match(&self, line: &str) -> bool {
722         self.parse_name_directive(line, "check-test-line-numbers-match")
723     }
724
725     fn parse_run_pass(&self, line: &str) -> bool {
726         self.parse_name_directive(line, "run-pass")
727     }
728
729     fn parse_skip_codegen(&self, line: &str) -> bool {
730         self.parse_name_directive(line, "skip-codegen")
731     }
732
733     fn parse_assembly_output(&self, line: &str) -> Option<String> {
734         self.parse_name_value_directive(line, "assembly-output")
735             .map(|r| r.trim().to_string())
736     }
737
738     fn parse_env(&self, line: &str, name: &str) -> Option<(String, String)> {
739         self.parse_name_value_directive(line, name).map(|nv| {
740             // nv is either FOO or FOO=BAR
741             let mut strs: Vec<String> = nv.splitn(2, '=').map(str::to_owned).collect();
742
743             match strs.len() {
744                 1 => (strs.pop().unwrap(), String::new()),
745                 2 => {
746                     let end = strs.pop().unwrap();
747                     (strs.pop().unwrap(), end)
748                 }
749                 n => panic!("Expected 1 or 2 strings, not {}", n),
750             }
751         })
752     }
753
754     fn parse_pp_exact(&self, line: &str, testfile: &Path) -> Option<PathBuf> {
755         if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
756             Some(PathBuf::from(&s))
757         } else if self.parse_name_directive(line, "pp-exact") {
758             testfile.file_name().map(PathBuf::from)
759         } else {
760             None
761         }
762     }
763
764     fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)> {
765         if self.parse_cfg_name_directive(line, prefix) == ParsedNameDirective::Match {
766             let from = parse_normalization_string(&mut line)?;
767             let to = parse_normalization_string(&mut line)?;
768             Some((from, to))
769         } else {
770             None
771         }
772     }
773
774     fn parse_needs_matching_clang(&self, line: &str) -> bool {
775         self.parse_name_directive(line, "needs-matching-clang")
776     }
777
778     fn parse_needs_profiler_support(&self, line: &str) -> bool {
779         self.parse_name_directive(line, "needs-profiler-support")
780     }
781
782     fn parse_needs_sanitizer_support(&self, line: &str) -> bool {
783         self.parse_name_directive(line, "needs-sanitizer-support")
784     }
785
786     /// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86`
787     /// or `normalize-stderr-32bit`.
788     fn parse_cfg_name_directive(&self, line: &str, prefix: &str) -> ParsedNameDirective {
789         if line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-') {
790             let name = line[prefix.len() + 1..]
791                 .split(&[':', ' '][..])
792                 .next()
793                 .unwrap();
794
795             if name == "test" ||
796                 util::matches_os(&self.target, name) ||             // target
797                 name == util::get_arch(&self.target) ||             // architecture
798                 name == util::get_pointer_width(&self.target) ||    // pointer width
799                 name == self.stage_id.split('-').next().unwrap() || // stage
800                 Some(name) == util::get_env(&self.target) ||        // env
801                 (self.target != self.host && name == "cross-compile") ||
802                 match self.compare_mode {
803                     Some(CompareMode::Nll) => name == "compare-mode-nll",
804                     Some(CompareMode::Polonius) => name == "compare-mode-polonius",
805                     None => false,
806                 } ||
807                 (cfg!(debug_assertions) && name == "debug") {
808                 ParsedNameDirective::Match
809             } else {
810                 match self.mode {
811                     common::DebugInfoGdbLldb => {
812                         if name == "gdb" {
813                             ParsedNameDirective::MatchGdb
814                         } else if name == "lldb" {
815                             ParsedNameDirective::MatchLldb
816                         } else {
817                             ParsedNameDirective::NoMatch
818                         }
819                     },
820                     common::DebugInfoCdb => if name == "cdb" {
821                         ParsedNameDirective::Match
822                     } else {
823                         ParsedNameDirective::NoMatch
824                     },
825                     common::DebugInfoGdb => if name == "gdb" {
826                         ParsedNameDirective::Match
827                     } else {
828                         ParsedNameDirective::NoMatch
829                     },
830                     common::DebugInfoLldb => if name == "lldb" {
831                         ParsedNameDirective::Match
832                     } else {
833                         ParsedNameDirective::NoMatch
834                     },
835                     common::Pretty => if name == "pretty" {
836                         ParsedNameDirective::Match
837                     } else {
838                         ParsedNameDirective::NoMatch
839                     },
840                     _ => ParsedNameDirective::NoMatch,
841                 }
842             }
843         } else {
844             ParsedNameDirective::NoMatch
845         }
846     }
847
848     fn has_cfg_prefix(&self, line: &str, prefix: &str) -> bool {
849         // returns whether this line contains this prefix or not. For prefix
850         // "ignore", returns true if line says "ignore-x86_64", "ignore-arch",
851         // "ignore-android" etc.
852         line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-')
853     }
854
855     fn parse_name_directive(&self, line: &str, directive: &str) -> bool {
856         // Ensure the directive is a whole word. Do not match "ignore-x86" when
857         // the line says "ignore-x86_64".
858         line.starts_with(directive) && match line.as_bytes().get(directive.len()) {
859             None | Some(&b' ') | Some(&b':') => true,
860             _ => false,
861         }
862     }
863
864     pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option<String> {
865         let colon = directive.len();
866         if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') {
867             let value = line[(colon + 1)..].to_owned();
868             debug!("{}: {}", directive, value);
869             Some(expand_variables(value, self))
870         } else {
871             None
872         }
873     }
874
875     pub fn find_rust_src_root(&self) -> Option<PathBuf> {
876         let mut path = self.src_base.clone();
877         let path_postfix = Path::new("src/etc/lldb_batchmode.py");
878
879         while path.pop() {
880             if path.join(&path_postfix).is_file() {
881                 return Some(path);
882             }
883         }
884
885         None
886     }
887
888     fn parse_run_rustfix(&self, line: &str) -> bool {
889         self.parse_name_directive(line, "run-rustfix")
890     }
891
892     fn parse_rustfix_only_machine_applicable(&self, line: &str) -> bool {
893         self.parse_name_directive(line, "rustfix-only-machine-applicable")
894     }
895
896     fn parse_edition(&self, line: &str) -> Option<String> {
897         self.parse_name_value_directive(line, "edition")
898     }
899 }
900
901 pub fn lldb_version_to_int(version_string: &str) -> isize {
902     let error_string = format!(
903         "Encountered LLDB version string with unexpected format: {}",
904         version_string
905     );
906     version_string.parse().expect(&error_string)
907 }
908
909 fn expand_variables(mut value: String, config: &Config) -> String {
910     const CWD: &'static str = "{{cwd}}";
911     const SRC_BASE: &'static str = "{{src-base}}";
912     const BUILD_BASE: &'static str = "{{build-base}}";
913
914     if value.contains(CWD) {
915         let cwd = env::current_dir().unwrap();
916         value = value.replace(CWD, &cwd.to_string_lossy());
917     }
918
919     if value.contains(SRC_BASE) {
920         value = value.replace(SRC_BASE, &config.src_base.to_string_lossy());
921     }
922
923     if value.contains(BUILD_BASE) {
924         value = value.replace(BUILD_BASE, &config.build_base.to_string_lossy());
925     }
926
927     value
928 }
929
930 /// Finds the next quoted string `"..."` in `line`, and extract the content from it. Move the `line`
931 /// variable after the end of the quoted string.
932 ///
933 /// # Examples
934 ///
935 /// ```
936 /// let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)\".";
937 /// let first = parse_normalization_string(&mut s);
938 /// assert_eq!(first, Some("something (32 bits)".to_owned()));
939 /// assert_eq!(s, " -> \"something ($WORD bits)\".");
940 /// ```
941 fn parse_normalization_string(line: &mut &str) -> Option<String> {
942     // FIXME support escapes in strings.
943     let begin = line.find('"')? + 1;
944     let end = line[begin..].find('"')? + begin;
945     let result = line[begin..end].to_owned();
946     *line = &line[end + 1..];
947     Some(result)
948 }
949
950 #[test]
951 fn test_parse_normalization_string() {
952     let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)\".";
953     let first = parse_normalization_string(&mut s);
954     assert_eq!(first, Some("something (32 bits)".to_owned()));
955     assert_eq!(s, " -> \"something ($WORD bits)\".");
956
957     // Nothing to normalize (No quotes)
958     let mut s = "normalize-stderr-32bit: something (32 bits) -> something ($WORD bits).";
959     let first = parse_normalization_string(&mut s);
960     assert_eq!(first, None);
961     assert_eq!(s, r#"normalize-stderr-32bit: something (32 bits) -> something ($WORD bits)."#);
962
963     // Nothing to normalize (Only a single quote)
964     let mut s = "normalize-stderr-32bit: \"something (32 bits) -> something ($WORD bits).";
965     let first = parse_normalization_string(&mut s);
966     assert_eq!(first, None);
967     assert_eq!(s, "normalize-stderr-32bit: \"something (32 bits) -> something ($WORD bits).");
968
969     // Nothing to normalize (Three quotes)
970     let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits).";
971     let first = parse_normalization_string(&mut s);
972     assert_eq!(first, Some("something (32 bits)".to_owned()));
973     assert_eq!(s, " -> \"something ($WORD bits).");
974 }