]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/header.rs
Rollup merge of #35558 - lukehinds:master, r=nikomatsakis
[rust.git] / src / tools / compiletest / src / header.rs
1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use std::env;
12 use std::fs::File;
13 use std::io::BufReader;
14 use std::io::prelude::*;
15 use std::path::{Path, PathBuf};
16
17 use common::Config;
18 use common;
19 use util;
20
21 /// Properties which must be known very early, before actually running
22 /// the test.
23 pub struct EarlyProps {
24     pub ignore: bool,
25     pub should_fail: bool,
26 }
27
28 impl EarlyProps {
29     pub fn from_file(config: &Config, testfile: &Path) -> Self {
30         let mut props = EarlyProps {
31             ignore: false,
32             should_fail: false,
33         };
34
35         iter_header(testfile,
36                     None,
37                     &mut |ln| {
38             props.ignore =
39                 props.ignore || parse_name_directive(ln, "ignore-test") ||
40                 parse_name_directive(ln, &ignore_target(config)) ||
41                 parse_name_directive(ln, &ignore_architecture(config)) ||
42                 parse_name_directive(ln, &ignore_stage(config)) ||
43                 parse_name_directive(ln, &ignore_env(config)) ||
44                 (config.mode == common::Pretty && parse_name_directive(ln, "ignore-pretty")) ||
45                 (config.target != config.host &&
46                  parse_name_directive(ln, "ignore-cross-compile")) ||
47                 ignore_gdb(config, ln) || ignore_lldb(config, ln);
48
49             props.should_fail = props.should_fail || parse_name_directive(ln, "should-fail");
50         });
51
52         return props;
53
54         fn ignore_target(config: &Config) -> String {
55             format!("ignore-{}", util::get_os(&config.target))
56         }
57         fn ignore_architecture(config: &Config) -> String {
58             format!("ignore-{}", util::get_arch(&config.target))
59         }
60         fn ignore_stage(config: &Config) -> String {
61             format!("ignore-{}", config.stage_id.split('-').next().unwrap())
62         }
63         fn ignore_env(config: &Config) -> String {
64             format!("ignore-{}",
65                     util::get_env(&config.target).unwrap_or("<unknown>"))
66         }
67         fn ignore_gdb(config: &Config, line: &str) -> bool {
68             if config.mode != common::DebugInfoGdb {
69                 return false;
70             }
71
72             if parse_name_directive(line, "ignore-gdb") {
73                 return true;
74             }
75
76             if let Some(ref actual_version) = config.gdb_version {
77                 if line.contains("min-gdb-version") {
78                     let min_version = line.trim()
79                         .split(' ')
80                         .last()
81                         .expect("Malformed GDB version directive");
82                     // Ignore if actual version is smaller the minimum required
83                     // version
84                     gdb_version_to_int(actual_version) < gdb_version_to_int(min_version)
85                 } else {
86                     false
87                 }
88             } else {
89                 false
90             }
91         }
92
93         fn ignore_lldb(config: &Config, line: &str) -> bool {
94             if config.mode != common::DebugInfoLldb {
95                 return false;
96             }
97
98             if parse_name_directive(line, "ignore-lldb") {
99                 return true;
100             }
101
102             if let Some(ref actual_version) = config.lldb_version {
103                 if line.contains("min-lldb-version") {
104                     let min_version = line.trim()
105                         .split(' ')
106                         .last()
107                         .expect("Malformed lldb version directive");
108                     // Ignore if actual version is smaller the minimum required
109                     // version
110                     lldb_version_to_int(actual_version) < lldb_version_to_int(min_version)
111                 } else {
112                     false
113                 }
114             } else {
115                 false
116             }
117         }
118     }
119 }
120
121 #[derive(Clone, Debug)]
122 pub struct TestProps {
123     // Lines that should be expected, in order, on standard out
124     pub error_patterns: Vec<String>,
125     // Extra flags to pass to the compiler
126     pub compile_flags: Vec<String>,
127     // Extra flags to pass when the compiled code is run (such as --bench)
128     pub run_flags: Option<String>,
129     // If present, the name of a file that this test should match when
130     // pretty-printed
131     pub pp_exact: Option<PathBuf>,
132     // Other crates that should be compiled (typically from the same
133     // directory as the test, but for backwards compatibility reasons
134     // we also check the auxiliary directory)
135     pub aux_builds: Vec<String>,
136     // Environment settings to use for compiling
137     pub rustc_env: Vec<(String, String)>,
138     // Environment settings to use during execution
139     pub exec_env: Vec<(String, String)>,
140     // Lines to check if they appear in the expected debugger output
141     pub check_lines: Vec<String>,
142     // Build documentation for all specified aux-builds as well
143     pub build_aux_docs: bool,
144     // Flag to force a crate to be built with the host architecture
145     pub force_host: bool,
146     // Check stdout for error-pattern output as well as stderr
147     pub check_stdout: bool,
148     // Don't force a --crate-type=dylib flag on the command line
149     pub no_prefer_dynamic: bool,
150     // Run --pretty expanded when running pretty printing tests
151     pub pretty_expanded: bool,
152     // Which pretty mode are we testing with, default to 'normal'
153     pub pretty_mode: String,
154     // Only compare pretty output and don't try compiling
155     pub pretty_compare_only: bool,
156     // Patterns which must not appear in the output of a cfail test.
157     pub forbid_output: Vec<String>,
158     // Revisions to test for incremental compilation.
159     pub revisions: Vec<String>,
160     // Directory (if any) to use for incremental compilation.  This is
161     // not set by end-users; rather it is set by the incremental
162     // testing harness and used when generating compilation
163     // arguments. (In particular, it propagates to the aux-builds.)
164     pub incremental_dir: Option<PathBuf>,
165 }
166
167 impl TestProps {
168     pub fn new() -> Self {
169         let error_patterns = Vec::new();
170         let aux_builds = Vec::new();
171         let exec_env = Vec::new();
172         let run_flags = None;
173         let pp_exact = None;
174         let check_lines = Vec::new();
175         let build_aux_docs = false;
176         let force_host = false;
177         let check_stdout = false;
178         let no_prefer_dynamic = false;
179         let pretty_expanded = false;
180         let pretty_compare_only = false;
181         let forbid_output = Vec::new();
182         TestProps {
183             error_patterns: error_patterns,
184             compile_flags: vec![],
185             run_flags: run_flags,
186             pp_exact: pp_exact,
187             aux_builds: aux_builds,
188             revisions: vec![],
189             rustc_env: vec![],
190             exec_env: exec_env,
191             check_lines: check_lines,
192             build_aux_docs: build_aux_docs,
193             force_host: force_host,
194             check_stdout: check_stdout,
195             no_prefer_dynamic: no_prefer_dynamic,
196             pretty_expanded: pretty_expanded,
197             pretty_mode: format!("normal"),
198             pretty_compare_only: pretty_compare_only,
199             forbid_output: forbid_output,
200             incremental_dir: None,
201         }
202     }
203
204     pub fn from_aux_file(&self, testfile: &Path, cfg: Option<&str>) -> Self {
205         let mut props = TestProps::new();
206
207         // copy over select properties to the aux build:
208         props.incremental_dir = self.incremental_dir.clone();
209         props.load_from(testfile, cfg);
210
211         props
212     }
213
214     pub fn from_file(testfile: &Path) -> Self {
215         let mut props = TestProps::new();
216         props.load_from(testfile, None);
217         props
218     }
219
220     /// Load properties from `testfile` into `props`. If a property is
221     /// tied to a particular revision `foo` (indicated by writing
222     /// `//[foo]`), then the property is ignored unless `cfg` is
223     /// `Some("foo")`.
224     pub fn load_from(&mut self, testfile: &Path, cfg: Option<&str>) {
225         iter_header(testfile,
226                     cfg,
227                     &mut |ln| {
228             if let Some(ep) = parse_error_pattern(ln) {
229                 self.error_patterns.push(ep);
230             }
231
232             if let Some(flags) = parse_compile_flags(ln) {
233                 self.compile_flags.extend(flags.split_whitespace()
234                     .map(|s| s.to_owned()));
235             }
236
237             if let Some(r) = parse_revisions(ln) {
238                 self.revisions.extend(r);
239             }
240
241             if self.run_flags.is_none() {
242                 self.run_flags = parse_run_flags(ln);
243             }
244
245             if self.pp_exact.is_none() {
246                 self.pp_exact = parse_pp_exact(ln, testfile);
247             }
248
249             if !self.build_aux_docs {
250                 self.build_aux_docs = parse_build_aux_docs(ln);
251             }
252
253             if !self.force_host {
254                 self.force_host = parse_force_host(ln);
255             }
256
257             if !self.check_stdout {
258                 self.check_stdout = parse_check_stdout(ln);
259             }
260
261             if !self.no_prefer_dynamic {
262                 self.no_prefer_dynamic = parse_no_prefer_dynamic(ln);
263             }
264
265             if !self.pretty_expanded {
266                 self.pretty_expanded = parse_pretty_expanded(ln);
267             }
268
269             if let Some(m) = parse_pretty_mode(ln) {
270                 self.pretty_mode = m;
271             }
272
273             if !self.pretty_compare_only {
274                 self.pretty_compare_only = parse_pretty_compare_only(ln);
275             }
276
277             if let Some(ab) = parse_aux_build(ln) {
278                 self.aux_builds.push(ab);
279             }
280
281             if let Some(ee) = parse_env(ln, "exec-env") {
282                 self.exec_env.push(ee);
283             }
284
285             if let Some(ee) = parse_env(ln, "rustc-env") {
286                 self.rustc_env.push(ee);
287             }
288
289             if let Some(cl) = parse_check_line(ln) {
290                 self.check_lines.push(cl);
291             }
292
293             if let Some(of) = parse_forbid_output(ln) {
294                 self.forbid_output.push(of);
295             }
296         });
297
298         for key in vec!["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
299             match env::var(key) {
300                 Ok(val) => {
301                     if self.exec_env.iter().find(|&&(ref x, _)| *x == key).is_none() {
302                         self.exec_env.push((key.to_owned(), val))
303                     }
304                 }
305                 Err(..) => {}
306             }
307         }
308     }
309 }
310
311 fn iter_header(testfile: &Path, cfg: Option<&str>, it: &mut FnMut(&str)) {
312     if testfile.is_dir() {
313         return;
314     }
315     let rdr = BufReader::new(File::open(testfile).unwrap());
316     for ln in rdr.lines() {
317         // Assume that any directives will be found before the first
318         // module or function. This doesn't seem to be an optimization
319         // with a warm page cache. Maybe with a cold one.
320         let ln = ln.unwrap();
321         let ln = ln.trim();
322         if ln.starts_with("fn") || ln.starts_with("mod") {
323             return;
324         } else if ln.starts_with("//[") {
325             // A comment like `//[foo]` is specific to revision `foo`
326             if let Some(close_brace) = ln.find("]") {
327                 let lncfg = &ln[3..close_brace];
328                 let matches = match cfg {
329                     Some(s) => s == &lncfg[..],
330                     None => false,
331                 };
332                 if matches {
333                     it(&ln[close_brace + 1..]);
334                 }
335             } else {
336                 panic!("malformed condition directive: expected `//[foo]`, found `{}`",
337                        ln)
338             }
339         } else if ln.starts_with("//") {
340             it(&ln[2..]);
341         }
342     }
343     return;
344 }
345
346 fn parse_error_pattern(line: &str) -> Option<String> {
347     parse_name_value_directive(line, "error-pattern")
348 }
349
350 fn parse_forbid_output(line: &str) -> Option<String> {
351     parse_name_value_directive(line, "forbid-output")
352 }
353
354 fn parse_aux_build(line: &str) -> Option<String> {
355     parse_name_value_directive(line, "aux-build")
356 }
357
358 fn parse_compile_flags(line: &str) -> Option<String> {
359     parse_name_value_directive(line, "compile-flags")
360 }
361
362 fn parse_revisions(line: &str) -> Option<Vec<String>> {
363     parse_name_value_directive(line, "revisions")
364         .map(|r| r.split_whitespace().map(|t| t.to_string()).collect())
365 }
366
367 fn parse_run_flags(line: &str) -> Option<String> {
368     parse_name_value_directive(line, "run-flags")
369 }
370
371 fn parse_check_line(line: &str) -> Option<String> {
372     parse_name_value_directive(line, "check")
373 }
374
375 fn parse_force_host(line: &str) -> bool {
376     parse_name_directive(line, "force-host")
377 }
378
379 fn parse_build_aux_docs(line: &str) -> bool {
380     parse_name_directive(line, "build-aux-docs")
381 }
382
383 fn parse_check_stdout(line: &str) -> bool {
384     parse_name_directive(line, "check-stdout")
385 }
386
387 fn parse_no_prefer_dynamic(line: &str) -> bool {
388     parse_name_directive(line, "no-prefer-dynamic")
389 }
390
391 fn parse_pretty_expanded(line: &str) -> bool {
392     parse_name_directive(line, "pretty-expanded")
393 }
394
395 fn parse_pretty_mode(line: &str) -> Option<String> {
396     parse_name_value_directive(line, "pretty-mode")
397 }
398
399 fn parse_pretty_compare_only(line: &str) -> bool {
400     parse_name_directive(line, "pretty-compare-only")
401 }
402
403 fn parse_env(line: &str, name: &str) -> Option<(String, String)> {
404     parse_name_value_directive(line, name).map(|nv| {
405         // nv is either FOO or FOO=BAR
406         let mut strs: Vec<String> = nv.splitn(2, '=')
407             .map(str::to_owned)
408             .collect();
409
410         match strs.len() {
411             1 => (strs.pop().unwrap(), "".to_owned()),
412             2 => {
413                 let end = strs.pop().unwrap();
414                 (strs.pop().unwrap(), end)
415             }
416             n => panic!("Expected 1 or 2 strings, not {}", n),
417         }
418     })
419 }
420
421 fn parse_pp_exact(line: &str, testfile: &Path) -> Option<PathBuf> {
422     if let Some(s) = parse_name_value_directive(line, "pp-exact") {
423         Some(PathBuf::from(&s))
424     } else {
425         if parse_name_directive(line, "pp-exact") {
426             testfile.file_name().map(PathBuf::from)
427         } else {
428             None
429         }
430     }
431 }
432
433 fn parse_name_directive(line: &str, directive: &str) -> bool {
434     // This 'no-' rule is a quick hack to allow pretty-expanded and no-pretty-expanded to coexist
435     line.contains(directive) && !line.contains(&("no-".to_owned() + directive))
436 }
437
438 pub fn parse_name_value_directive(line: &str, directive: &str) -> Option<String> {
439     let keycolon = format!("{}:", directive);
440     if let Some(colon) = line.find(&keycolon) {
441         let value = line[(colon + keycolon.len())..line.len()].to_owned();
442         debug!("{}: {}", directive, value);
443         Some(value)
444     } else {
445         None
446     }
447 }
448
449 pub fn gdb_version_to_int(version_string: &str) -> isize {
450     let error_string = format!("Encountered GDB version string with unexpected format: {}",
451                                version_string);
452     let error_string = error_string;
453
454     let components: Vec<&str> = version_string.trim().split('.').collect();
455
456     if components.len() != 2 {
457         panic!("{}", error_string);
458     }
459
460     let major: isize = components[0].parse().ok().expect(&error_string);
461     let minor: isize = components[1].parse().ok().expect(&error_string);
462
463     return major * 1000 + minor;
464 }
465
466 pub fn lldb_version_to_int(version_string: &str) -> isize {
467     let error_string = format!("Encountered LLDB version string with unexpected format: {}",
468                                version_string);
469     let error_string = error_string;
470     let major: isize = version_string.parse().ok().expect(&error_string);
471     return major;
472 }