about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--COMPILER_TESTS.md40
-rw-r--r--src/compiletest/compiletest.rs18
-rw-r--r--src/compiletest/errors.rs55
-rw-r--r--src/compiletest/header.rs228
-rw-r--r--src/compiletest/runtest.rs291
-rw-r--r--src/test/compile-fail/coherence-cow.rs (renamed from src/test/compile-fail/coherence-cow-1.rs)13
-rw-r--r--src/test/compile-fail/meta-expected-error-correct-rev.rs (renamed from src/test/compile-fail/coherence-cow-no-cover.rs)16
-rw-r--r--src/test/compile-fail/meta-expected-error-wrong-rev.rs (renamed from src/test/compile-fail/coherence-cow-2.rs)22
-rw-r--r--src/test/run-fail/meta-revision-bad.rs22
-rw-r--r--src/test/run-fail/meta-revision-ok.rs21
10 files changed, 498 insertions, 228 deletions
diff --git a/COMPILER_TESTS.md b/COMPILER_TESTS.md
index e2a957e3961..1cae4ef090f 100644
--- a/COMPILER_TESTS.md
+++ b/COMPILER_TESTS.md
@@ -42,3 +42,43 @@ whole, instead of just a few lines inside the test.
 * `ignore-test` always ignores the test
 * `ignore-lldb` and `ignore-gdb` will skip the debuginfo tests
 * `min-{gdb,lldb}-version`
+* `should-fail` indicates that the test should fail; used for "meta testing",
+  where we test the compiletest program itself to check that it will generate
+  errors in appropriate scenarios. This header is ignored for pretty-printer tests.
+
+## Revisions
+
+Certain classes of tests support "revisions" (as of the time of this
+writing, this includes run-pass, compile-fail, run-fail, and
+incremental, though incremental tests are somewhat
+different). Revisions allow a single test file to be used for multiple
+tests. This is done by adding a special header at the top of the file:
+
+```
+// revisions: foo bar baz
+```
+
+This will result in the test being compiled (and tested) three times,
+once with `--cfg foo`, once with `--cfg bar`, and once with `--cfg
+baz`. You can therefore use `#[cfg(foo)]` etc within the test to tweak
+each of these results.
+
+You can also customize headers and expected error messages to a particular
+revision. To do this, add `[foo]` (or `bar`, `baz`, etc) after the `//`
+comment, like so:
+
+```
+// A flag to pass in only for cfg `foo`:
+//[foo]compile-flags: -Z verbose
+
+#[cfg(foo)]
+fn test_foo() {
+    let x: usize = 32_u32; //[foo]~ ERROR mismatched types
+}
+```
+
+Note that not all headers have meaning when customized too a revision.
+For example, the `ignore-test` header (and all "ignore" headers)
+currently only apply to the test as a whole, not to particular
+revisions. The only headers that are intended to really work when
+customized to a revision are error patterns and compiler flags.
diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs
index bbace16f059..99745d840f7 100644
--- a/src/compiletest/compiletest.rs
+++ b/src/compiletest/compiletest.rs
@@ -354,11 +354,25 @@ pub fn is_test(config: &Config, testfile: &Path) -> bool {
 }
 
 pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn {
+    let early_props = header::early_props(config, &testpaths.file);
+
+    // The `should-fail` annotation doesn't apply to pretty tests,
+    // since we run the pretty printer across all tests by default.
+    // If desired, we could add a `should-fail-pretty` annotation.
+    let should_panic = match config.mode {
+        Pretty => test::ShouldPanic::No,
+        _ => if early_props.should_fail {
+            test::ShouldPanic::Yes
+        } else {
+            test::ShouldPanic::No
+        }
+    };
+
     test::TestDescAndFn {
         desc: test::TestDesc {
             name: make_test_name(config, testpaths),
-            ignore: header::is_test_ignored(config, &testpaths.file),
-            should_panic: test::ShouldPanic::No,
+            ignore: early_props.ignore,
+            should_panic: should_panic,
         },
         testfn: make_test_closure(config, testpaths),
     }
diff --git a/src/compiletest/errors.rs b/src/compiletest/errors.rs
index a3ad022ebd5..44634e4d565 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<ExpectedError> {
+///
+/// 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<&str>) -> Vec<ExpectedError> {
     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<ExpectedError> {
     // 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(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<usize>,
                   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<usize>,
         (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 6efe6e608e8..cf4d545a827 100644
--- a/src/compiletest/header.rs
+++ b/src/compiletest/header.rs
@@ -18,11 +18,12 @@ use common::Config;
 use common;
 use util;
 
+#[derive(Clone, Debug)]
 pub struct TestProps {
     // Lines that should be expected, in order, on standard out
     pub error_patterns: Vec<String> ,
     // Extra flags to pass to the compiler
-    pub compile_flags: Option<String>,
+    pub compile_flags: Vec<String>,
     // Extra flags to pass when the compiled code is run (such as --bench)
     pub run_flags: Option<String>,
     // If present, the name of a file that this test should match when
@@ -50,119 +51,168 @@ pub struct TestProps {
     pub pretty_compare_only: bool,
     // Patterns which must not appear in the output of a cfail test.
     pub forbid_output: Vec<String>,
+    // Revisions to test for incremental compilation.
+    pub revisions: Vec<String>,
 }
 
 // Load any test directives embedded in the file
 pub fn load_props(testfile: &Path) -> TestProps {
-    let mut error_patterns = Vec::new();
-    let mut aux_builds = Vec::new();
-    let mut exec_env = Vec::new();
-    let mut compile_flags = None;
-    let mut run_flags = None;
-    let mut pp_exact = None;
-    let mut check_lines = Vec::new();
-    let mut build_aux_docs = false;
-    let mut force_host = false;
-    let mut check_stdout = false;
-    let mut no_prefer_dynamic = false;
-    let mut pretty_expanded = false;
-    let mut pretty_mode = None;
-    let mut pretty_compare_only = false;
-    let mut forbid_output = Vec::new();
-    iter_header(testfile, &mut |ln| {
+    let error_patterns = Vec::new();
+    let aux_builds = Vec::new();
+    let exec_env = Vec::new();
+    let run_flags = None;
+    let pp_exact = None;
+    let check_lines = Vec::new();
+    let build_aux_docs = false;
+    let force_host = false;
+    let check_stdout = false;
+    let no_prefer_dynamic = false;
+    let pretty_expanded = false;
+    let pretty_compare_only = false;
+    let forbid_output = Vec::new();
+    let mut props = TestProps {
+        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,
+        force_host: force_host,
+        check_stdout: check_stdout,
+        no_prefer_dynamic: no_prefer_dynamic,
+        pretty_expanded: pretty_expanded,
+        pretty_mode: format!("normal"),
+        pretty_compare_only: pretty_compare_only,
+        forbid_output: forbid_output,
+    };
+    load_props_into(&mut props, testfile, None);
+    props
+}
+
+/// 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) {
-           error_patterns.push(ep);
+            props.error_patterns.push(ep);
+        }
+
+        if let Some(flags) = parse_compile_flags(ln) {
+            props.compile_flags.extend(
+                flags
+                    .split_whitespace()
+                    .map(|s| s.to_owned()));
         }
 
-        if compile_flags.is_none() {
-            compile_flags = parse_compile_flags(ln);
+        if let Some(r) = parse_revisions(ln) {
+            props.revisions.extend(r);
         }
 
-        if run_flags.is_none() {
-            run_flags = parse_run_flags(ln);
+        if props.run_flags.is_none() {
+            props.run_flags = parse_run_flags(ln);
         }
 
-        if pp_exact.is_none() {
-            pp_exact = parse_pp_exact(ln, testfile);
+        if props.pp_exact.is_none() {
+            props.pp_exact = parse_pp_exact(ln, testfile);
         }
 
-        if !build_aux_docs {
-            build_aux_docs = parse_build_aux_docs(ln);
+        if !props.build_aux_docs {
+            props.build_aux_docs = parse_build_aux_docs(ln);
         }
 
-        if !force_host {
-            force_host = parse_force_host(ln);
+        if !props.force_host {
+            props.force_host = parse_force_host(ln);
         }
 
-        if !check_stdout {
-            check_stdout = parse_check_stdout(ln);
+        if !props.check_stdout {
+            props.check_stdout = parse_check_stdout(ln);
         }
 
-        if !no_prefer_dynamic {
-            no_prefer_dynamic = parse_no_prefer_dynamic(ln);
+        if !props.no_prefer_dynamic {
+            props.no_prefer_dynamic = parse_no_prefer_dynamic(ln);
         }
 
-        if !pretty_expanded {
-            pretty_expanded = parse_pretty_expanded(ln);
+        if !props.pretty_expanded {
+            props.pretty_expanded = parse_pretty_expanded(ln);
         }
 
-        if pretty_mode.is_none() {
-            pretty_mode = parse_pretty_mode(ln);
+        if let Some(m) = parse_pretty_mode(ln) {
+            props.pretty_mode = m;
         }
 
-        if !pretty_compare_only {
-            pretty_compare_only = parse_pretty_compare_only(ln);
+        if !props.pretty_compare_only {
+            props.pretty_compare_only = parse_pretty_compare_only(ln);
         }
 
         if let  Some(ab) = parse_aux_build(ln) {
-            aux_builds.push(ab);
+            props.aux_builds.push(ab);
         }
 
         if let Some(ee) = parse_exec_env(ln) {
-            exec_env.push(ee);
+            props.exec_env.push(ee);
         }
 
         if let Some(cl) =  parse_check_line(ln) {
-            check_lines.push(cl);
+            props.check_lines.push(cl);
         }
 
         if let Some(of) = parse_forbid_output(ln) {
-            forbid_output.push(of);
+            props.forbid_output.push(of);
         }
-
-        true
     });
 
     for key in vec!["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] {
         match env::var(key) {
             Ok(val) =>
-                if exec_env.iter().find(|&&(ref x, _)| *x == key).is_none() {
-                    exec_env.push((key.to_owned(), val))
+                if props.exec_env.iter().find(|&&(ref x, _)| *x == key).is_none() {
+                    props.exec_env.push((key.to_owned(), val))
                 },
             Err(..) => {}
         }
     }
+}
 
-    TestProps {
-        error_patterns: error_patterns,
-        compile_flags: compile_flags,
-        run_flags: run_flags,
-        pp_exact: pp_exact,
-        aux_builds: aux_builds,
-        exec_env: exec_env,
-        check_lines: check_lines,
-        build_aux_docs: build_aux_docs,
-        force_host: force_host,
-        check_stdout: check_stdout,
-        no_prefer_dynamic: no_prefer_dynamic,
-        pretty_expanded: pretty_expanded,
-        pretty_mode: pretty_mode.unwrap_or("normal".to_owned()),
-        pretty_compare_only: pretty_compare_only,
-        forbid_output: forbid_output,
-    }
+pub struct EarlyProps {
+    pub ignore: bool,
+    pub should_fail: bool,
 }
 
-pub fn is_test_ignored(config: &Config, testfile: &Path) -> bool {
+// scan the file to detect whether the test should be ignored and
+// whether it should panic; these are two things the test runner needs
+// to know early, before actually running the test
+pub fn early_props(config: &Config, testfile: &Path) -> EarlyProps {
+    let mut props = EarlyProps {
+        ignore: false,
+        should_fail: false,
+    };
+
+    iter_header(testfile, None, &mut |ln| {
+        props.ignore =
+            props.ignore ||
+            parse_name_directive(ln, "ignore-test") ||
+            parse_name_directive(ln, &ignore_target(config)) ||
+            parse_name_directive(ln, &ignore_architecture(config)) ||
+            parse_name_directive(ln, &ignore_stage(config)) ||
+            parse_name_directive(ln, &ignore_env(config)) ||
+            (config.mode == common::Pretty &&
+             parse_name_directive(ln, "ignore-pretty")) ||
+            (config.target != config.host &&
+             parse_name_directive(ln, "ignore-cross-compile")) ||
+            ignore_gdb(config, ln) ||
+            ignore_lldb(config, ln);
+
+        props.should_fail =
+            props.should_fail ||
+            parse_name_directive(ln, "should-fail");
+    });
+
+    return props;
+
     fn ignore_target(config: &Config) -> String {
         format!("ignore-{}", util::get_os(&config.target))
     }
@@ -229,39 +279,40 @@ pub fn is_test_ignored(config: &Config, testfile: &Path) -> bool {
             false
         }
     }
-
-    let val = iter_header(testfile, &mut |ln| {
-        !parse_name_directive(ln, "ignore-test") &&
-        !parse_name_directive(ln, &ignore_target(config)) &&
-        !parse_name_directive(ln, &ignore_architecture(config)) &&
-        !parse_name_directive(ln, &ignore_stage(config)) &&
-        !parse_name_directive(ln, &ignore_env(config)) &&
-        !(config.mode == common::Pretty && parse_name_directive(ln, "ignore-pretty")) &&
-        !(config.target != config.host && parse_name_directive(ln, "ignore-cross-compile")) &&
-        !ignore_gdb(config, ln) &&
-        !ignore_lldb(config, ln)
-    });
-
-    !val
 }
 
-fn iter_header(testfile: &Path, it: &mut FnMut(&str) -> bool) -> bool {
+fn iter_header(testfile: &Path,
+               cfg: Option<&str>,
+               it: &mut FnMut(&str)) {
     let rdr = BufReader::new(File::open(testfile).unwrap());
     for ln in rdr.lines() {
         // Assume that any directives will be found before the first
         // module or function. This doesn't seem to be an optimization
         // with a warm page cache. Maybe with a cold one.
         let ln = ln.unwrap();
-        if ln.starts_with("fn") ||
-                ln.starts_with("mod") {
-            return true;
-        } else {
-            if !(it(ln.trim())) {
-                return false;
+        let ln = ln.trim();
+        if ln.starts_with("fn") || ln.starts_with("mod") {
+            return;
+        } 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..]);
+                }
+            } else {
+                panic!("malformed condition directive: expected `//[foo]`, found `{}`",
+                       ln)
             }
+        } else if ln.starts_with("//") {
+            it(&ln[2..]);
         }
     }
-    return true;
+    return;
 }
 
 fn parse_error_pattern(line: &str) -> Option<String> {
@@ -280,6 +331,11 @@ fn parse_compile_flags(line: &str) -> Option<String> {
     parse_name_value_directive(line, "compile-flags")
 }
 
+fn parse_revisions(line: &str) -> Option<Vec<String>> {
+    parse_name_value_directive(line, "revisions")
+        .map(|r| r.split_whitespace().map(|t| t.to_string()).collect())
+}
+
 fn parse_run_flags(line: &str) -> Option<String> {
     parse_name_value_directive(line, "run-flags")
 }
diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs
index 7cad5a4391c..1d2f560f5f6 100644
--- a/src/compiletest/runtest.rs
+++ b/src/compiletest/runtest.rs
@@ -70,39 +70,77 @@ fn get_output(props: &TestProps, proc_res: &ProcRes) -> String {
     }
 }
 
+
+fn for_each_revision<OP>(config: &Config, props: &TestProps, testpaths: &TestPaths,
+                         mut op: OP)
+    where OP: FnMut(&Config, &TestProps, &TestPaths, Option<&str>)
+{
+    if props.revisions.is_empty() {
+        op(config, props, testpaths, None)
+    } else {
+        for revision in &props.revisions {
+            let mut revision_props = props.clone();
+            header::load_props_into(&mut revision_props,
+                                    &testpaths.file,
+                                    Some(&revision));
+            revision_props.compile_flags.extend(vec![
+                format!("--cfg"),
+                format!("{}", revision),
+            ]);
+            op(config, &revision_props, testpaths, Some(revision));
+        }
+    }
+}
+
 fn run_cfail_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    for_each_revision(config, props, testpaths, run_cfail_test_revision);
+}
+
+fn run_cfail_test_revision(config: &Config,
+                           props: &TestProps,
+                           testpaths: &TestPaths,
+                           revision: Option<&str>) {
     let proc_res = compile_test(config, props, testpaths);
 
     if proc_res.status.success() {
-        fatal_proc_rec(&format!("{} test compiled successfully!", config.mode)[..],
-                      &proc_res);
+        fatal_proc_rec(
+            revision,
+            &format!("{} test compiled successfully!", config.mode)[..],
+            &proc_res);
     }
 
-    check_correct_failure_status(&proc_res);
+    check_correct_failure_status(revision, &proc_res);
 
     if proc_res.status.success() {
-        fatal("process did not return an error status");
+        fatal(revision, "process did not return an error status");
     }
 
     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, revision);
     if !expected_errors.is_empty() {
         if !props.error_patterns.is_empty() {
-            fatal("both error pattern and expected errors specified");
+            fatal(revision, "both error pattern and expected errors specified");
         }
-        check_expected_errors(expected_errors, testpaths, &proc_res);
+        check_expected_errors(revision, expected_errors, testpaths, &proc_res);
     } else {
-        check_error_patterns(props, testpaths, &output_to_check, &proc_res);
+        check_error_patterns(revision, props, testpaths, &output_to_check, &proc_res);
     }
-    check_no_compiler_crash(&proc_res);
-    check_forbid_output(props, &output_to_check, &proc_res);
+    check_no_compiler_crash(revision, &proc_res);
+    check_forbid_output(revision, props, &output_to_check, &proc_res);
 }
 
 fn run_rfail_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    for_each_revision(config, props, testpaths, run_rfail_test_revision);
+}
+
+fn run_rfail_test_revision(config: &Config,
+                           props: &TestProps,
+                           testpaths: &TestPaths,
+                           revision: Option<&str>) {
     let proc_res = compile_test(config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("compilation failed!", &proc_res);
+        fatal_proc_rec(revision, "compilation failed!", &proc_res);
     }
 
     let proc_res = exec_compiled_test(config, props, testpaths);
@@ -110,19 +148,20 @@ fn run_rfail_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
     // The value our Makefile configures valgrind to return on failure
     const VALGRIND_ERR: i32 = 100;
     if proc_res.status.code() == Some(VALGRIND_ERR) {
-        fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res);
+        fatal_proc_rec(revision, "run-fail test isn't valgrind-clean!", &proc_res);
     }
 
     let output_to_check = get_output(props, &proc_res);
-    check_correct_failure_status(&proc_res);
-    check_error_patterns(props, testpaths, &output_to_check, &proc_res);
+    check_correct_failure_status(revision, &proc_res);
+    check_error_patterns(revision, props, testpaths, &output_to_check, &proc_res);
 }
 
-fn check_correct_failure_status(proc_res: &ProcRes) {
+fn check_correct_failure_status(revision: Option<&str>, proc_res: &ProcRes) {
     // The value the rust runtime returns on failure
     const RUST_ERR: i32 = 101;
     if proc_res.status.code() != Some(RUST_ERR) {
         fatal_proc_rec(
+            revision,
             &format!("failure produced the wrong error: {}",
                      proc_res.status),
             proc_res);
@@ -130,20 +169,29 @@ fn check_correct_failure_status(proc_res: &ProcRes) {
 }
 
 fn run_rpass_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    for_each_revision(config, props, testpaths, run_rpass_test_revision);
+}
+
+fn run_rpass_test_revision(config: &Config,
+                           props: &TestProps,
+                           testpaths: &TestPaths,
+                           revision: Option<&str>) {
     let proc_res = compile_test(config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("compilation failed!", &proc_res);
+        fatal_proc_rec(revision, "compilation failed!", &proc_res);
     }
 
     let proc_res = exec_compiled_test(config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("test run failed!", &proc_res);
+        fatal_proc_rec(revision, "test run failed!", &proc_res);
     }
 }
 
 fn run_valgrind_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
+
     if config.valgrind_path.is_none() {
         assert!(!config.force_valgrind);
         return run_rpass_test(config, props, testpaths);
@@ -152,7 +200,7 @@ fn run_valgrind_test(config: &Config, props: &TestProps, testpaths: &TestPaths)
     let mut proc_res = compile_test(config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("compilation failed!", &proc_res);
+        fatal_proc_rec(None, "compilation failed!", &proc_res);
     }
 
     let mut new_config = config.clone();
@@ -160,11 +208,18 @@ fn run_valgrind_test(config: &Config, props: &TestProps, testpaths: &TestPaths)
     proc_res = exec_compiled_test(&new_config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("test run failed!", &proc_res);
+        fatal_proc_rec(None, "test run failed!", &proc_res);
     }
 }
 
 fn run_pretty_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    for_each_revision(config, props, testpaths, run_pretty_test_revision);
+}
+
+fn run_pretty_test_revision(config: &Config,
+                            props: &TestProps,
+                            testpaths: &TestPaths,
+                            revision: Option<&str>) {
     if props.pp_exact.is_some() {
         logv(config, "testing for exact pretty-printing".to_owned());
     } else {
@@ -180,7 +235,8 @@ fn run_pretty_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
 
     let mut round = 0;
     while round < rounds {
-        logv(config, format!("pretty-printing round {}", round));
+        logv(config, format!("pretty-printing round {} revision {:?}",
+                             round, revision));
         let proc_res = print_source(config,
                                     props,
                                     testpaths,
@@ -188,8 +244,10 @@ fn run_pretty_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
                                     &props.pretty_mode);
 
         if !proc_res.status.success() {
-            fatal_proc_rec(&format!("pretty-printing failed in round {}", round),
-                          &proc_res);
+            fatal_proc_rec(revision,
+                           &format!("pretty-printing failed in round {} revision {:?}",
+                                    round, revision),
+                           &proc_res);
         }
 
         let ProcRes{ stdout, .. } = proc_res;
@@ -215,30 +273,32 @@ fn run_pretty_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
         expected = expected.replace(&cr, "").to_owned();
     }
 
-    compare_source(&expected, &actual);
+    compare_source(revision, &expected, &actual);
 
     // If we're only making sure that the output matches then just stop here
     if props.pretty_compare_only { return; }
 
     // Finally, let's make sure it actually appears to remain valid code
     let proc_res = typecheck_source(config, props, testpaths, actual);
-
     if !proc_res.status.success() {
-        fatal_proc_rec("pretty-printed source does not typecheck", &proc_res);
+        fatal_proc_rec(revision, "pretty-printed source does not typecheck", &proc_res);
     }
+
     if !props.pretty_expanded { return }
 
     // additionally, run `--pretty expanded` and try to build it.
     let proc_res = print_source(config, props, testpaths, srcs[round].clone(), "expanded");
     if !proc_res.status.success() {
-        fatal_proc_rec("pretty-printing (expanded) failed", &proc_res);
+        fatal_proc_rec(revision, "pretty-printing (expanded) failed", &proc_res);
     }
 
     let ProcRes{ stdout: expanded_src, .. } = proc_res;
     let proc_res = typecheck_source(config, props, testpaths, expanded_src);
     if !proc_res.status.success() {
-        fatal_proc_rec("pretty-printed source (expanded) does not typecheck",
-                      &proc_res);
+        fatal_proc_rec(
+            revision,
+            "pretty-printed source (expanded) does not typecheck",
+            &proc_res);
     }
 
     return;
@@ -275,16 +335,16 @@ fn run_pretty_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
                             "-L".to_owned(),
                             aux_dir.to_str().unwrap().to_owned());
         args.extend(split_maybe_args(&config.target_rustcflags));
-        args.extend(split_maybe_args(&props.compile_flags));
+        args.extend(props.compile_flags.iter().cloned());
         return ProcArgs {
             prog: config.rustc_path.to_str().unwrap().to_owned(),
             args: args,
         };
     }
 
-    fn compare_source(expected: &str, actual: &str) {
+    fn compare_source(revision: Option<&str>, expected: &str, actual: &str) {
         if expected != actual {
-            error("pretty-printed source does not match expected source");
+            error(revision, "pretty-printed source does not match expected source");
             println!("\n\
 expected:\n\
 ------------------------------------------\n\
@@ -322,7 +382,7 @@ actual:\n\
                             "-L".to_owned(),
                             aux_dir.to_str().unwrap().to_owned());
         args.extend(split_maybe_args(&config.target_rustcflags));
-        args.extend(split_maybe_args(&props.compile_flags));
+        args.extend(props.compile_flags.iter().cloned());
         // FIXME (#9639): This needs to handle non-utf8 paths
         return ProcArgs {
             prog: config.rustc_path.to_str().unwrap().to_owned(),
@@ -332,6 +392,8 @@ actual:\n\
 }
 
 fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
+
     let mut config = Config {
         target_rustcflags: cleanup_debug_info_options(&config.target_rustcflags),
         host_rustcflags: cleanup_debug_info_options(&config.host_rustcflags),
@@ -349,7 +411,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testpaths: &TestPa
     // compile test file (it should have 'compile-flags:-g' in the header)
     let compiler_run_result = compile_test(config, props, testpaths);
     if !compiler_run_result.status.success() {
-        fatal_proc_rec("compilation failed!", &compiler_run_result);
+        fatal_proc_rec(None, "compilation failed!", &compiler_run_result);
     }
 
     let exe_file = make_exe_name(config, testpaths);
@@ -441,7 +503,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testpaths: &TestPa
 
             let tool_path = match config.android_cross_path.to_str() {
                 Some(x) => x.to_owned(),
-                None => fatal("cannot find android cross path")
+                None => fatal(None, "cannot find android cross path")
             };
 
             let debugger_script = make_out_name(config, testpaths, "debugger.script");
@@ -580,7 +642,7 @@ fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testpaths: &TestPa
     }
 
     if !debugger_run_result.status.success() {
-        fatal("gdb failed to execute");
+        fatal(None, "gdb failed to execute");
     }
 
     check_debugger_output(&debugger_run_result, &check_lines);
@@ -600,8 +662,10 @@ fn find_rust_src_root(config: &Config) -> Option<PathBuf> {
 }
 
 fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
+
     if config.lldb_python_dir.is_none() {
-        fatal("Can't run LLDB test because LLDB's python path is not set.");
+        fatal(None, "Can't run LLDB test because LLDB's python path is not set.");
     }
 
     let mut config = Config {
@@ -615,7 +679,7 @@ fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testpaths: &TestP
     // compile test file (it should have 'compile-flags:-g' in the header)
     let compile_result = compile_test(config, props, testpaths);
     if !compile_result.status.success() {
-        fatal_proc_rec("compilation failed!", &compile_result);
+        fatal_proc_rec(None, "compilation failed!", &compile_result);
     }
 
     let exe_file = make_exe_name(config, testpaths);
@@ -692,7 +756,7 @@ fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testpaths: &TestP
                                        &rust_src_root);
 
     if !debugger_run_result.status.success() {
-        fatal_proc_rec("Error while running LLDB", &debugger_run_result);
+        fatal_proc_rec(None, "Error while running LLDB", &debugger_run_result);
     }
 
     check_debugger_output(&debugger_run_result, &check_lines);
@@ -725,7 +789,7 @@ fn cmd2procres(config: &Config, testpaths: &TestPaths, cmd: &mut Command)
              String::from_utf8(stderr).unwrap())
         },
         Err(e) => {
-            fatal(&format!("Failed to setup Python process for \
+            fatal(None, &format!("Failed to setup Python process for \
                             LLDB script: {}", e))
         }
     };
@@ -775,7 +839,7 @@ fn parse_debugger_commands(testpaths: &TestPaths, debugger_prefix: &str)
                 });
             }
             Err(e) => {
-                fatal(&format!("Error while parsing debugger commands: {}", e))
+                fatal(None, &format!("Error while parsing debugger commands: {}", e))
             }
         }
         counter += 1;
@@ -857,19 +921,21 @@ fn check_debugger_output(debugger_run_result: &ProcRes, check_lines: &[String])
             }
         }
         if i != num_check_lines {
-            fatal_proc_rec(&format!("line not found in debugger output: {}",
+            fatal_proc_rec(None, &format!("line not found in debugger output: {}",
                                     check_lines.get(i).unwrap()),
                           debugger_run_result);
         }
     }
 }
 
-fn check_error_patterns(props: &TestProps,
+fn check_error_patterns(revision: Option<&str>,
+                        props: &TestProps,
                         testpaths: &TestPaths,
                         output_to_check: &str,
                         proc_res: &ProcRes) {
     if props.error_patterns.is_empty() {
-        fatal(&format!("no error pattern specified in {:?}",
+        fatal(revision,
+              &format!("no error pattern specified in {:?}",
                        testpaths.file.display()));
     }
     let mut next_err_idx = 0;
@@ -891,44 +957,50 @@ fn check_error_patterns(props: &TestProps,
 
     let missing_patterns = &props.error_patterns[next_err_idx..];
     if missing_patterns.len() == 1 {
-        fatal_proc_rec(&format!("error pattern '{}' not found!", missing_patterns[0]),
-                      proc_res);
+        fatal_proc_rec(
+            revision,
+            &format!("error pattern '{}' not found!", missing_patterns[0]),
+            proc_res);
     } else {
         for pattern in missing_patterns {
-            error(&format!("error pattern '{}' not found!", *pattern));
+            error(revision, &format!("error pattern '{}' not found!", *pattern));
         }
-        fatal_proc_rec("multiple error patterns not found", proc_res);
+        fatal_proc_rec(revision, "multiple error patterns not found", proc_res);
     }
 }
 
-fn check_no_compiler_crash(proc_res: &ProcRes) {
+fn check_no_compiler_crash(revision: Option<&str>, proc_res: &ProcRes) {
     for line in proc_res.stderr.lines() {
         if line.starts_with("error: internal compiler error:") {
-            fatal_proc_rec("compiler encountered internal error",
-                          proc_res);
+            fatal_proc_rec(revision,
+                           "compiler encountered internal error",
+                           proc_res);
         }
     }
 }
 
-fn check_forbid_output(props: &TestProps,
+fn check_forbid_output(revision: Option<&str>,
+                       props: &TestProps,
                        output_to_check: &str,
                        proc_res: &ProcRes) {
     for pat in &props.forbid_output {
         if output_to_check.contains(pat) {
-            fatal_proc_rec("forbidden pattern found in compiler output", proc_res);
+            fatal_proc_rec(revision,
+                           "forbidden pattern found in compiler output",
+                           proc_res);
         }
     }
 }
 
-fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
+fn check_expected_errors(revision: Option<&str>,
+                         expected_errors: Vec<errors::ExpectedError>,
                          testpaths: &TestPaths,
                          proc_res: &ProcRes) {
-
     // true if we found the error in question
     let mut found_flags = vec![false; expected_errors.len()];
 
     if proc_res.status.success() {
-        fatal("process did not return an error status");
+        fatal_proc_rec(revision, "process did not return an error status", proc_res);
     }
 
     let prefixes = expected_errors.iter().map(|ee| {
@@ -944,23 +1016,6 @@ fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
                                   (acc_help || ee.kind == "help:", acc_note ||
                                    ee.kind == "note:"));
 
-    fn prefix_matches(line: &str, prefix: &str) -> bool {
-        use std::ascii::AsciiExt;
-        // On windows just translate all '\' path separators to '/'
-        let line = line.replace(r"\", "/");
-        if cfg!(windows) {
-            line.to_ascii_lowercase().starts_with(&prefix.to_ascii_lowercase())
-        } else {
-            line.starts_with(prefix)
-        }
-    }
-
-    // A multi-line error will have followup lines which start with a space
-    // or open paren.
-    fn continuation( line: &str) -> bool {
-        line.starts_with(" ") || line.starts_with("(")
-    }
-
     // Scan and extract our error/warning messages,
     // which look like:
     //    filename:line1:col1: line2:col2: *error:* msg
@@ -970,6 +1025,8 @@ fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
     //
     // This pattern is ambiguous on windows, because filename may contain
     // a colon, so any path prefix must be detected and removed first.
+    let mut unexpected = 0;
+    let mut not_found = 0;
     for line in proc_res.stderr.lines() {
         let mut was_expected = false;
         let mut prev = 0;
@@ -991,9 +1048,11 @@ fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
                         break;
                     }
                 }
-                if (prefix_matches(line, &prefixes[i]) || continuation(line)) &&
+                if
+                    (prefix_matches(line, &prefixes[i]) || continuation(line)) &&
                     line.contains(&ee.kind) &&
-                    line.contains(&ee.msg) {
+                    line.contains(&ee.msg)
+                {
                     found_flags[i] = true;
                     was_expected = true;
                     break;
@@ -1008,20 +1067,44 @@ fn check_expected_errors(expected_errors: Vec<errors::ExpectedError>,
         }
 
         if !was_expected && is_unexpected_compiler_message(line, expect_help, expect_note) {
-            fatal_proc_rec(&format!("unexpected compiler message: '{}'",
-                                    line),
-                          proc_res);
+            error(revision, &format!("unexpected compiler message: '{}'", line));
+            unexpected += 1;
         }
     }
 
     for (i, &flag) in found_flags.iter().enumerate() {
         if !flag {
             let ee = &expected_errors[i];
-            fatal_proc_rec(&format!("expected {} on line {} not found: {}",
-                                    ee.kind, ee.line, ee.msg),
-                          proc_res);
+            error(revision, &format!("expected {} on line {} not found: {}",
+                                     ee.kind, ee.line, ee.msg));
+            not_found += 1;
         }
     }
+
+    if unexpected > 0 || not_found > 0 {
+        fatal_proc_rec(
+            revision,
+            &format!("{} unexpected errors found, {} expected errors not found",
+                     unexpected, not_found),
+            proc_res);
+    }
+
+    fn prefix_matches(line: &str, prefix: &str) -> bool {
+        use std::ascii::AsciiExt;
+        // On windows just translate all '\' path separators to '/'
+        let line = line.replace(r"\", "/");
+        if cfg!(windows) {
+            line.to_ascii_lowercase().starts_with(&prefix.to_ascii_lowercase())
+        } else {
+            line.starts_with(prefix)
+        }
+    }
+
+    // A multi-line error will have followup lines which start with a space
+    // or open paren.
+    fn continuation( line: &str) -> bool {
+        line.starts_with(" ") || line.starts_with("(")
+    }
 }
 
 fn is_unexpected_compiler_message(line: &str, expect_help: bool, expect_note: bool) -> bool {
@@ -1184,7 +1267,7 @@ fn document(config: &Config,
                         "-o".to_owned(),
                         out_dir.to_str().unwrap().to_owned(),
                         testpaths.file.to_str().unwrap().to_owned()];
-    args.extend(split_maybe_args(&props.compile_flags));
+    args.extend(props.compile_flags.iter().cloned());
     let args = ProcArgs {
         prog: config.rustdoc_path.to_str().unwrap().to_owned(),
         args: args,
@@ -1286,6 +1369,7 @@ fn compose_and_run_compiler(config: &Config, props: &TestProps,
                                      None);
         if !auxres.status.success() {
             fatal_proc_rec(
+                None,
                 &format!("auxiliary build of {:?} failed to compile: ",
                         aux_testpaths.file.display()),
                 &auxres);
@@ -1369,7 +1453,7 @@ fn make_compile_args<F>(config: &Config,
     } else {
         args.extend(split_maybe_args(&config.target_rustcflags));
     }
-    args.extend(split_maybe_args(&props.compile_flags));
+    args.extend(props.compile_flags.iter().cloned());
     return ProcArgs {
         prog: config.rustc_path.to_str().unwrap().to_owned(),
         args: args,
@@ -1537,13 +1621,20 @@ fn maybe_dump_to_stdout(config: &Config, out: &str, err: &str) {
     }
 }
 
-fn error(err: &str) { println!("\nerror: {}", err); }
+fn error(revision: Option<&str>, err: &str) {
+    match revision {
+        Some(rev) => println!("\nerror in revision `{}`: {}", rev, err),
+        None => println!("\nerror: {}", err)
+    }
+}
 
-fn fatal(err: &str) -> ! { error(err); panic!(); }
+fn fatal(revision: Option<&str>, err: &str) -> ! {
+    error(revision, err); panic!();
+}
 
-fn fatal_proc_rec(err: &str, proc_res: &ProcRes) -> ! {
-    print!("\n\
-error: {}\n\
+fn fatal_proc_rec(revision: Option<&str>, err: &str, proc_res: &ProcRes) -> ! {
+    error(revision, err);
+    print!("\
 status: {}\n\
 command: {}\n\
 stdout:\n\
@@ -1555,7 +1646,7 @@ stderr:\n\
 {}\n\
 ------------------------------------------\n\
 \n",
-             err, proc_res.status, proc_res.cmdline, proc_res.stdout,
+             proc_res.status, proc_res.cmdline, proc_res.stdout,
              proc_res.stderr);
     panic!();
 }
@@ -1753,20 +1844,22 @@ fn check_ir_with_filecheck(config: &Config, testpaths: &TestPaths) -> ProcRes {
 }
 
 fn run_codegen_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
 
     if config.llvm_bin_path.is_none() {
-        fatal("missing --llvm-bin-path");
+        fatal(None, "missing --llvm-bin-path");
     }
 
     let mut proc_res = compile_test_and_save_ir(config, props, testpaths);
     if !proc_res.status.success() {
-        fatal_proc_rec("compilation failed!", &proc_res);
+        fatal_proc_rec(None, "compilation failed!", &proc_res);
     }
 
     proc_res = check_ir_with_filecheck(config, testpaths);
     if !proc_res.status.success() {
-        fatal_proc_rec("verification with 'FileCheck' failed",
-                      &proc_res);
+        fatal_proc_rec(None,
+                       "verification with 'FileCheck' failed",
+                       &proc_res);
     }
 }
 
@@ -1782,13 +1875,15 @@ fn charset() -> &'static str {
 }
 
 fn run_rustdoc_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
+
     let out_dir = output_base_name(config, testpaths);
     let _ = fs::remove_dir_all(&out_dir);
     ensure_dir(&out_dir);
 
     let proc_res = document(config, props, testpaths, &out_dir);
     if !proc_res.status.success() {
-        fatal_proc_rec("rustdoc failed!", &proc_res);
+        fatal_proc_rec(None, "rustdoc failed!", &proc_res);
     }
     let root = find_rust_src_root(config).unwrap();
 
@@ -1799,18 +1894,20 @@ fn run_rustdoc_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
                                   .arg(out_dir)
                                   .arg(&testpaths.file));
     if !res.status.success() {
-        fatal_proc_rec("htmldocck failed!", &res);
+        fatal_proc_rec(None, "htmldocck failed!", &res);
     }
 }
 
 fn run_codegen_units_test(config: &Config, props: &TestProps, testpaths: &TestPaths) {
+    assert!(props.revisions.is_empty(), "revisions not relevant here");
+
     let proc_res = compile_test(config, props, testpaths);
 
     if !proc_res.status.success() {
-        fatal_proc_rec("compilation failed!", &proc_res);
+        fatal_proc_rec(None, "compilation failed!", &proc_res);
     }
 
-    check_no_compiler_crash(&proc_res);
+    check_no_compiler_crash(None, &proc_res);
 
     let prefix = "TRANS_ITEM ";
 
@@ -1821,7 +1918,7 @@ fn run_codegen_units_test(config: &Config, props: &TestProps, testpaths: &TestPa
         .map(|s| (&s[prefix.len()..]).to_string())
         .collect();
 
-    let expected: HashSet<String> = errors::load_errors(&testpaths.file)
+    let expected: HashSet<String> = errors::load_errors(&testpaths.file, None)
         .iter()
         .map(|e| e.msg.trim().to_string())
         .collect();
diff --git a/src/test/compile-fail/coherence-cow-1.rs b/src/test/compile-fail/coherence-cow.rs
index 530bbf57d91..6a2d1bac493 100644
--- a/src/test/compile-fail/coherence-cow-1.rs
+++ b/src/test/compile-fail/coherence-cow.rs
@@ -8,6 +8,8 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+// revisions: a b c
+
 // aux-build:coherence_lib.rs
 
 // pretty-expanded FIXME #23616
@@ -22,7 +24,14 @@ use lib::{Remote,Pair};
 
 pub struct Cover<T>(T);
 
-impl<T> Remote for Pair<T,Cover<T>> { }
-//~^ ERROR E0210
+#[cfg(a)]
+impl<T> Remote for Pair<T,Cover<T>> { } //[a]~ ERROR E0210
+
+#[cfg(b)]
+impl<T> Remote for Pair<Cover<T>,T> { } //[b]~ ERROR E0210
+
+#[cfg(c)]
+impl<T,U> Remote for Pair<Cover<T>,U> { }
+//[c]~^ ERROR type parameter `T` must be used as the type parameter for some local type
 
 fn main() { }
diff --git a/src/test/compile-fail/coherence-cow-no-cover.rs b/src/test/compile-fail/meta-expected-error-correct-rev.rs
index cd32e797ae9..95b4e1a33cc 100644
--- a/src/test/compile-fail/coherence-cow-no-cover.rs
+++ b/src/test/compile-fail/meta-expected-error-correct-rev.rs
@@ -8,16 +8,14 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// aux-build:coherence_lib.rs
+// revisions: a
+// pretty-expanded FIXME #23616
 
-// Test that it's not ok for T to appear uncovered
+// Counterpart to `meta-expected-error-wrong-rev.rs`
 
-extern crate coherence_lib as lib;
-use lib::{Remote,Pair};
-
-pub struct Cover<T>(T);
-
-impl<T,U> Remote for Pair<Cover<T>,U> { }
-//~^ ERROR type parameter `T` must be used as the type parameter for some local type
+#[cfg(a)]
+fn foo() {
+    let x: u32 = 22_usize; //[a]~ ERROR mismatched types
+}
 
 fn main() { }
diff --git a/src/test/compile-fail/coherence-cow-2.rs b/src/test/compile-fail/meta-expected-error-wrong-rev.rs
index 52abceab98b..084c6ed4f4b 100644
--- a/src/test/compile-fail/coherence-cow-2.rs
+++ b/src/test/compile-fail/meta-expected-error-wrong-rev.rs
@@ -8,20 +8,18 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// aux-build:coherence_lib.rs
-
-// Test that the `Pair` type reports an error if it contains type
-// parameters, even when they are covered by local types. This test
-// was originally intended to test the opposite, but the rules changed
-// with RFC 1023 and this became illegal.
-
+// revisions: a
+// should-fail
 // pretty-expanded FIXME #23616
 
-extern crate coherence_lib as lib;
-use lib::{Remote,Pair};
-
-pub struct Cover<T>(T);
+// This is a "meta-test" of the compilertest framework itself.  In
+// particular, it includes the right error message, but the message
+// targets the wrong revision, so we expect the execution to fail.
+// See also `meta-expected-error-correct-rev.rs`.
 
-impl<T> Remote for Pair<Cover<T>,T> { } //~ ERROR E0210
+#[cfg(a)]
+fn foo() {
+    let x: u32 = 22_usize; //[b]~ ERROR mismatched types
+}
 
 fn main() { }
diff --git a/src/test/run-fail/meta-revision-bad.rs b/src/test/run-fail/meta-revision-bad.rs
new file mode 100644
index 00000000000..bf521d4b4e5
--- /dev/null
+++ b/src/test/run-fail/meta-revision-bad.rs
@@ -0,0 +1,22 @@
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Meta test for compiletest: check that when we give the wrong error
+// patterns, the test fails.
+
+// revisions: foo bar
+// should-fail
+//[foo] error-pattern:bar
+//[bar] error-pattern:foo
+
+#[cfg(foo)] fn die() {panic!("foo");}
+#[cfg(bar)] fn die() {panic!("bar");}
+
+fn main() { die(); }
diff --git a/src/test/run-fail/meta-revision-ok.rs b/src/test/run-fail/meta-revision-ok.rs
new file mode 100644
index 00000000000..f74ec39fdf2
--- /dev/null
+++ b/src/test/run-fail/meta-revision-ok.rs
@@ -0,0 +1,21 @@
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Meta test for compiletest: check that when we give the right error
+// patterns, the test passes. See all `meta-revision-bad.rs`.
+
+// revisions: foo bar
+//[foo] error-pattern:foo
+//[bar] error-pattern:bar
+
+#[cfg(foo)] fn die() {panic!("foo");}
+#[cfg(bar)] fn die() {panic!("bar");}
+
+fn main() { die(); }