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