From bb2a425d5861297a9a5d08b8d5826b7703ecea04 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 1 Mar 2016 21:10:36 -0500 Subject: [PATCH] introduce the notion of revisions, currently unused a test file may specify `// revisions: foo bar baz` headers and expected errors may be made specific to a revision by writing `//[foo] header` or `//[foo]~ ERROR` --- src/compiletest/errors.rs | 55 +++++++++++++++++++++++-------------- src/compiletest/header.rs | 56 ++++++++++++++++++++++++++++++++++---- src/compiletest/runtest.rs | 4 +-- 3 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/compiletest/errors.rs b/src/compiletest/errors.rs index a3ad022ebd5..63bc657baa4 100644 --- a/src/compiletest/errors.rs +++ b/src/compiletest/errors.rs @@ -30,8 +30,10 @@ enum WhichLine { ThisLine, FollowPrevious(usize), AdjustBackward(usize) } /// Goal is to enable tests both like: //~^^^ ERROR go up three /// and also //~^ ERROR message one for the preceding line, and /// //~| ERROR message two for that same line. -// Load any test directives embedded in the file -pub fn load_errors(testfile: &Path) -> Vec { +/// +/// If cfg is not None (i.e., in an incremental test), then we look +/// for `//[X]~` instead, where `X` is the current `cfg`. +pub fn load_errors(testfile: &Path, cfg: &Option) -> Vec { let rdr = BufReader::new(File::open(testfile).unwrap()); // `last_nonfollow_error` tracks the most recently seen @@ -44,30 +46,41 @@ pub fn load_errors(testfile: &Path) -> Vec { // updating it in the map callback below.) let mut last_nonfollow_error = None; - rdr.lines().enumerate().filter_map(|(line_no, ln)| { - parse_expected(last_nonfollow_error, - line_no + 1, - &ln.unwrap()) - .map(|(which, error)| { - match which { - FollowPrevious(_) => {} - _ => last_nonfollow_error = Some(error.line), - } - error - }) - }).collect() + let tag = match *cfg { + Some(ref rev) => format!("//[{}]~", rev), + None => format!("//~") + }; + + rdr.lines() + .enumerate() + .filter_map(|(line_no, ln)| { + parse_expected(last_nonfollow_error, + line_no + 1, + &ln.unwrap(), + &tag) + .map(|(which, error)| { + match which { + FollowPrevious(_) => {} + _ => last_nonfollow_error = Some(error.line), + } + error + }) + }) + .collect() } fn parse_expected(last_nonfollow_error: Option, line_num: usize, - line: &str) -> Option<(WhichLine, ExpectedError)> { - let start = match line.find("//~") { Some(i) => i, None => return None }; - let (follow, adjusts) = if line.char_at(start + 3) == '|' { + line: &str, + tag: &str) + -> Option<(WhichLine, ExpectedError)> { + let start = match line.find(tag) { Some(i) => i, None => return None }; + let (follow, adjusts) = if line.char_at(start + tag.len()) == '|' { (true, 0) } else { - (false, line[start + 3..].chars().take_while(|c| *c == '^').count()) + (false, line[start + tag.len()..].chars().take_while(|c| *c == '^').count()) }; - let kind_start = start + 3 + adjusts + (follow as usize); + let kind_start = start + tag.len() + adjusts + (follow as usize); let letters = line[kind_start..].chars(); let kind = letters.skip_while(|c| c.is_whitespace()) .take_while(|c| !c.is_whitespace()) @@ -91,7 +104,9 @@ fn parse_expected(last_nonfollow_error: Option, (which, line) }; - debug!("line={} which={:?} kind={:?} msg={:?}", line_num, which, kind, msg); + debug!("line={} tag={:?} which={:?} kind={:?} msg={:?}", + line_num, tag, which, kind, msg); + Some((which, ExpectedError { line: line, kind: kind, msg: msg, })) diff --git a/src/compiletest/header.rs b/src/compiletest/header.rs index 26567e12ea7..c9dfc0bf1a4 100644 --- a/src/compiletest/header.rs +++ b/src/compiletest/header.rs @@ -18,7 +18,18 @@ use common::Config; use common; use util; +#[derive(Clone, Debug)] pub struct TestProps { + // For the main test file, this is initialized to `None`. But + // when running tests that test multiple revisions, such as + // incremental tests, we will set this to `Some(foo)` where `foo` + // is the current revision identifier. + // + // Note that, unlike the other options here, this value is never + // loaded from the input file (though it is always set to one of + // the values listed in the vec `self.revisions`, which is loaded + // from the file). + pub revision: Option, // Lines that should be expected, in order, on standard out pub error_patterns: Vec , // Extra flags to pass to the compiler @@ -50,6 +61,8 @@ pub struct TestProps { pub pretty_compare_only: bool, // Patterns which must not appear in the output of a cfail test. pub forbid_output: Vec, + // Revisions to test for incremental compilation. + pub revisions: Vec, } // Load any test directives embedded in the file @@ -68,11 +81,13 @@ pub fn load_props(testfile: &Path) -> TestProps { let pretty_compare_only = false; let forbid_output = Vec::new(); let mut props = TestProps { + revision: None, error_patterns: error_patterns, compile_flags: vec![], run_flags: run_flags, pp_exact: pp_exact, aux_builds: aux_builds, + revisions: vec![], exec_env: exec_env, check_lines: check_lines, build_aux_docs: build_aux_docs, @@ -84,12 +99,16 @@ pub fn load_props(testfile: &Path) -> TestProps { pretty_compare_only: pretty_compare_only, forbid_output: forbid_output, }; - load_props_into(&mut props, testfile); + load_props_into(&mut props, testfile, None); props } -pub fn load_props_into(props: &mut TestProps, testfile: &Path) { - iter_header(testfile, &mut |ln| { +/// Load properties from `testfile` into `props`. If a property is +/// tied to a particular revision `foo` (indicated by writing +/// `//[foo]`), then the property is ignored unless `cfg` is +/// `Some("foo")`. +pub fn load_props_into(props: &mut TestProps, testfile: &Path, cfg: Option<&str>) { + iter_header(testfile, cfg, &mut |ln| { if let Some(ep) = parse_error_pattern(ln) { props.error_patterns.push(ep); } @@ -101,6 +120,10 @@ pub fn load_props_into(props: &mut TestProps, testfile: &Path) { .map(|s| s.to_owned())); } + if let Some(r) = parse_revisions(ln) { + props.revisions.extend(r); + } + if props.run_flags.is_none() { props.run_flags = parse_run_flags(ln); } @@ -235,7 +258,7 @@ pub fn is_test_ignored(config: &Config, testfile: &Path) -> bool { } } - let val = iter_header(testfile, &mut |ln| { + let val = iter_header(testfile, None, &mut |ln| { !parse_name_directive(ln, "ignore-test") && !parse_name_directive(ln, &ignore_target(config)) && !parse_name_directive(ln, &ignore_architecture(config)) && @@ -250,7 +273,10 @@ pub fn is_test_ignored(config: &Config, testfile: &Path) -> bool { !val } -fn iter_header(testfile: &Path, it: &mut FnMut(&str) -> bool) -> bool { +fn iter_header(testfile: &Path, + cfg: Option<&str>, + it: &mut FnMut(&str) -> bool) + -> bool { let rdr = BufReader::new(File::open(testfile).unwrap()); for ln in rdr.lines() { // Assume that any directives will be found before the first @@ -260,6 +286,21 @@ fn iter_header(testfile: &Path, it: &mut FnMut(&str) -> bool) -> bool { let ln = ln.trim(); if ln.starts_with("fn") || ln.starts_with("mod") { return true; + } else if ln.starts_with("//[") { + // A comment like `//[foo]` is specific to revision `foo` + if let Some(close_brace) = ln.find("]") { + let lncfg = &ln[3..close_brace]; + let matches = match cfg { + Some(s) => s == &lncfg[..], + None => false, + }; + if matches && !it(&ln[close_brace+1..]) { + return false; + } + } else { + panic!("malformed condition directive: expected `//[foo]`, found `{}`", + ln) + } } else if ln.starts_with("//") { if !it(&ln[2..]) { return false; @@ -285,6 +326,11 @@ fn parse_compile_flags(line: &str) -> Option { parse_name_value_directive(line, "compile-flags") } +fn parse_revisions(line: &str) -> Option> { + parse_name_value_directive(line, "revisions") + .map(|r| r.split_whitespace().map(|t| t.to_string()).collect()) +} + fn parse_run_flags(line: &str) -> Option { parse_name_value_directive(line, "run-flags") } diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 4ebfce86381..ab766eecd9d 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -85,7 +85,7 @@ fn run_cfail_test(config: &Config, props: &TestProps, testpaths: &TestPaths) { } let output_to_check = get_output(props, &proc_res); - let expected_errors = errors::load_errors(&testpaths.file); + let expected_errors = errors::load_errors(&testpaths.file, &props.revision); if !expected_errors.is_empty() { if !props.error_patterns.is_empty() { fatal("both error pattern and expected errors specified"); @@ -1821,7 +1821,7 @@ fn run_codegen_units_test(config: &Config, props: &TestProps, testpaths: &TestPa .map(|s| (&s[prefix.len()..]).to_string()) .collect(); - let expected: HashSet = errors::load_errors(&testpaths.file) + let expected: HashSet = errors::load_errors(&testpaths.file, &props.revision) .iter() .map(|e| e.msg.trim().to_string()) .collect();