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