diff --git a/COMPILER_TESTS.md b/COMPILER_TESTS.md index e2a957e396191..1cae4ef090fe0 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 bbace16f05928..99745d840f767 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 a3ad022ebd52f..44634e4d565ff 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<&str>) -> 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(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 6efe6e608e8ad..cf4d545a827c1 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 , // Extra flags to pass to the compiler - pub compile_flags: Option, + pub compile_flags: Vec, // Extra flags to pass when the compiled code is run (such as --bench) pub run_flags: Option, // 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, + // Revisions to test for incremental compilation. + pub revisions: Vec, } // 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 { @@ -280,6 +331,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 7cad5a4391c01..1d2f560f5f65b 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(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 { } 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, +fn check_expected_errors(revision: Option<&str>, + expected_errors: Vec, 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, (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, // // 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, 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, } 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(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 = errors::load_errors(&testpaths.file) + let expected: HashSet = 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 similarity index 74% rename from src/test/compile-fail/coherence-cow-1.rs rename to src/test/compile-fail/coherence-cow.rs index 530bbf57d9104..6a2d1bac49381 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); -impl Remote for Pair> { } -//~^ ERROR E0210 +#[cfg(a)] +impl Remote for Pair> { } //[a]~ ERROR E0210 + +#[cfg(b)] +impl Remote for Pair,T> { } //[b]~ ERROR E0210 + +#[cfg(c)] +impl Remote for Pair,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 similarity index 62% rename from src/test/compile-fail/coherence-cow-no-cover.rs rename to src/test/compile-fail/meta-expected-error-correct-rev.rs index cd32e797ae9bf..95b4e1a33cccd 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); - -impl Remote for Pair,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 similarity index 55% rename from src/test/compile-fail/coherence-cow-2.rs rename to src/test/compile-fail/meta-expected-error-wrong-rev.rs index 52abceab98b69..084c6ed4f4b41 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); +// 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 Remote for Pair,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 0000000000000..bf521d4b4e5e4 --- /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 or the MIT license +// , 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 0000000000000..f74ec39fdf27a --- /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 or the MIT license +// , 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(); }