diff --git a/src/Cargo.lock b/src/Cargo.lock index 4e16e61aa0d39..fa5823acefbdc 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -2770,6 +2770,7 @@ name = "syntax_ext" version = "0.0.0" dependencies = [ "fmt_macros 0.0.0", + "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "proc_macro 0.0.0", "rustc_data_structures 0.0.0", "rustc_errors 0.0.0", diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs index c26d8555214c1..a203af3e4678f 100644 --- a/src/librustc_lint/builtin.rs +++ b/src/librustc_lint/builtin.rs @@ -1842,43 +1842,56 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns { } declare_lint! { - UNNAMEABLE_TEST_FUNCTIONS, + UNNAMEABLE_TEST_ITEMS, Warn, - "detects an function that cannot be named being marked as #[test]" + "detects an item that cannot be named being marked as #[test_case]", + report_in_external_macro: true } -pub struct UnnameableTestFunctions; +pub struct UnnameableTestItems { + boundary: ast::NodeId, // NodeId of the item under which things are not nameable + items_nameable: bool, +} + +impl UnnameableTestItems { + pub fn new() -> Self { + Self { + boundary: ast::DUMMY_NODE_ID, + items_nameable: true + } + } +} -impl LintPass for UnnameableTestFunctions { +impl LintPass for UnnameableTestItems { fn get_lints(&self) -> LintArray { - lint_array!(UNNAMEABLE_TEST_FUNCTIONS) + lint_array!(UNNAMEABLE_TEST_ITEMS) } } -impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestFunctions { +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems { fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { - match it.node { - hir::ItemKind::Fn(..) => { - for attr in &it.attrs { - if attr.name() == "test" { - let parent = cx.tcx.hir.get_parent(it.id); - match cx.tcx.hir.find(parent) { - Some(hir_map::NodeItem(hir::Item {node: hir::ItemKind::Mod(_), ..})) | - None => {} - _ => { - cx.struct_span_lint( - UNNAMEABLE_TEST_FUNCTIONS, - attr.span, - "cannot test inner function", - ).emit(); - } - } - break; - } - } + if self.items_nameable { + if let hir::ItemKind::Mod(..) = it.node {} + else { + self.items_nameable = false; + self.boundary = it.id; } - _ => return, - }; + return; + } + + if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") { + cx.struct_span_lint( + UNNAMEABLE_TEST_ITEMS, + attr.span, + "cannot test inner items", + ).emit(); + } + } + + fn check_item_post(&mut self, _cx: &LateContext, it: &hir::Item) { + if !self.items_nameable && self.boundary == it.id { + self.items_nameable = true; + } } } diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index 15eb4730c1d20..7bef5599021ff 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -148,7 +148,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) { MutableTransmutes: MutableTransmutes, UnionsWithDropFields: UnionsWithDropFields, UnreachablePub: UnreachablePub, - UnnameableTestFunctions: UnnameableTestFunctions, + UnnameableTestItems: UnnameableTestItems::new(), TypeAliasBounds: TypeAliasBounds, UnusedBrokenConst: UnusedBrokenConst, TrivialConstraints: TrivialConstraints, diff --git a/src/librustc_resolve/macros.rs b/src/librustc_resolve/macros.rs index 1161d57417b18..b79982ce51e6f 100644 --- a/src/librustc_resolve/macros.rs +++ b/src/librustc_resolve/macros.rs @@ -475,6 +475,10 @@ impl<'a, 'cl> Resolver<'a, 'cl> { return def; } + if kind == MacroKind::Attr && *&path[0].as_str() == "test" { + return Ok(self.macro_prelude.get(&path[0].name).unwrap().def()) + } + let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false); let result = if let Some((legacy_binding, _)) = legacy_resolution { Ok(legacy_binding.def()) diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index ec6ac86ba6bd3..b3154af4ed139 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -1582,7 +1582,7 @@ impl TyKind { if let TyKind::ImplicitSelf = *self { true } else { false } } - crate fn is_unit(&self) -> bool { + pub fn is_unit(&self) -> bool { if let TyKind::Tup(ref tys) = *self { tys.is_empty() } else { false } } } diff --git a/src/libsyntax/config.rs b/src/libsyntax/config.rs index 0e52434ec0170..900d830b4c038 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -119,7 +119,7 @@ impl<'a> StripUnconfigured<'a> { pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool { attrs.iter().all(|attr| { // When not compiling with --test we should not compile the #[test] functions - if !self.should_test && is_test_or_bench(attr) { + if !self.should_test && is_test(attr) { return false; } @@ -249,7 +249,7 @@ impl<'a> StripUnconfigured<'a> { // // NB: This is intentionally not part of the fold_expr() function // in order for fold_opt_expr() to be able to avoid this check - if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test_or_bench(a)) { + if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) { let msg = "removing an expression is not supported in this position"; self.sess.span_diagnostic.span_err(attr.span, msg); } @@ -353,6 +353,6 @@ fn is_cfg(attr: &ast::Attribute) -> bool { attr.check_name("cfg") } -pub fn is_test_or_bench(attr: &ast::Attribute) -> bool { - attr.check_name("test") || attr.check_name("bench") +pub fn is_test(att: &ast::Attribute) -> bool { + att.check_name("test_case") } diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 97279e00869c6..1a71ab8f07ad8 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -12,10 +12,9 @@ use ast::{self, Block, Ident, NodeId, PatKind, Path}; use ast::{MacStmtStyle, StmtKind, ItemKind}; use attr::{self, HasAttrs}; use source_map::{ExpnInfo, MacroBang, MacroAttribute, dummy_spanned, respan}; -use config::{is_test_or_bench, StripUnconfigured}; +use config::StripUnconfigured; use errors::{Applicability, FatalError}; use ext::base::*; -use ext::build::AstBuilder; use ext::derive::{add_derived_markers, collect_derives}; use ext::hygiene::{self, Mark, SyntaxContext}; use ext::placeholders::{placeholder, PlaceholderExpander}; @@ -1370,51 +1369,25 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { self.cx.current_expansion.directory_ownership = orig_directory_ownership; result } - // Ensure that test functions are accessible from the test harness. + + // Ensure that test items can be exported by the harness generator. // #[test] fn foo() {} // becomes: // #[test] pub fn foo_gensym(){} - // #[allow(unused)] - // use foo_gensym as foo; - ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => { - if self.tests_nameable && item.attrs.iter().any(|attr| is_test_or_bench(attr)) { - let orig_ident = item.ident; - let orig_vis = item.vis.clone(); - + ast::ItemKind::Const(..) + | ast::ItemKind::Static(..) + | ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => { + if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") { // Publicize the item under gensymed name to avoid pollution + // This means #[test_case] items can't be referenced by user code item = item.map(|mut item| { item.vis = respan(item.vis.span, ast::VisibilityKind::Public); item.ident = item.ident.gensym(); item }); - - // Use the gensymed name under the item's original visibility - let mut use_item = self.cx.item_use_simple_( - item.ident.span, - orig_vis, - Some(orig_ident), - self.cx.path(item.ident.span, - vec![keywords::SelfValue.ident(), item.ident])); - - // #[allow(unused)] because the test function probably isn't being referenced - use_item = use_item.map(|mut ui| { - ui.attrs.push( - self.cx.attribute(DUMMY_SP, attr::mk_list_item(DUMMY_SP, - Ident::from_str("allow"), vec![ - attr::mk_nested_word_item(Ident::from_str("unused")) - ] - )) - ); - - ui - }); - - OneVector::many( - self.fold_unnameable(item).into_iter() - .chain(self.fold_unnameable(use_item))) - } else { - self.fold_unnameable(item) } + + self.fold_unnameable(item) } _ => self.fold_unnameable(item), } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 71ad118ed8eac..258b73a6c06af 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -503,6 +503,10 @@ declare_features! ( // unsized rvalues at arguments and parameters (active, unsized_locals, "1.30.0", Some(48055), None), + + // #![test_runner] + // #[test_case] + (active, custom_test_frameworks, "1.30.0", Some(50297), None), ); declare_features! ( @@ -757,6 +761,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("no_link", Normal, Ungated), ("derive", Normal, Ungated), ("should_panic", Normal, Ungated), + ("test_case", Normal, Gated(Stability::Unstable, + "custom_test_frameworks", + "Custom test frameworks are experimental", + cfg_fn!(custom_test_frameworks))), ("ignore", Normal, Ungated), ("no_implicit_prelude", Normal, Ungated), ("reexport_test_harness_main", Normal, Ungated), @@ -1123,6 +1131,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("no_builtins", CrateLevel, Ungated), ("recursion_limit", CrateLevel, Ungated), ("type_length_limit", CrateLevel, Ungated), + ("test_runner", CrateLevel, Gated(Stability::Unstable, + "custom_test_frameworks", + "Custom Test Frameworks is an unstable feature", + cfg_fn!(custom_test_frameworks))), ]; // cfg(...)'s that are feature gated diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 988f50b4f0c9e..d01420884e604 100644 --- a/src/libsyntax/test.rs +++ b/src/libsyntax/test.rs @@ -22,7 +22,7 @@ use std::vec; use attr::{self, HasAttrs}; use syntax_pos::{self, DUMMY_SP, NO_EXPANSION, Span, SourceFile, BytePos}; -use source_map::{self, SourceMap, ExpnInfo, MacroAttribute, dummy_spanned}; +use source_map::{self, SourceMap, ExpnInfo, MacroAttribute, dummy_spanned, respan}; use errors; use config; use entry::{self, EntryPointType}; @@ -42,29 +42,21 @@ use OneVector; use symbol::{self, Symbol, keywords}; use ThinVec; -enum ShouldPanic { - No, - Yes(Option), -} - struct Test { span: Span, - path: Vec , - bench: bool, - ignore: bool, - should_panic: ShouldPanic, - allow_fail: bool, + path: Vec, } struct TestCtxt<'a> { span_diagnostic: &'a errors::Handler, path: Vec, ext_cx: ExtCtxt<'a>, - testfns: Vec, + test_cases: Vec, reexport_test_harness_main: Option, is_libtest: bool, ctxt: SyntaxContext, features: &'a Features, + test_runner: Option, // top-level re-export submodule, filled out after folding is finished toplevel_reexport: Option, @@ -86,9 +78,13 @@ pub fn modify_for_testing(sess: &ParseSess, attr::first_attr_value_str_by_name(&krate.attrs, "reexport_test_harness_main"); + // Do this here so that the test_runner crate attribute gets marked as used + // even in non-test builds + let test_runner = get_test_runner(span_diagnostic, &krate); + if should_test { generate_test_harness(sess, resolver, reexport_test_harness_main, - krate, span_diagnostic, features) + krate, span_diagnostic, features, test_runner) } else { krate } @@ -106,13 +102,13 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate { let mut folded = fold::noop_fold_crate(c, self); - // Add a special __test module to the crate that will contain code - // generated for the test harness - let (mod_, reexport) = mk_test_module(&mut self.cx); - if let Some(re) = reexport { - folded.module.items.push(re) - } - folded.module.items.push(mod_); + // Create a main function to run our tests + let test_main = { + let unresolved = mk_main(&mut self.cx); + self.cx.ext_cx.monotonic_expander().fold_item(unresolved).pop().unwrap() + }; + + folded.module.items.push(test_main); folded } @@ -123,41 +119,18 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { } debug!("current path: {}", path_name_i(&self.cx.path)); - if is_test_fn(&self.cx, &i) || is_bench_fn(&self.cx, &i) { - match i.node { - ast::ItemKind::Fn(_, header, _, _) => { - if header.unsafety == ast::Unsafety::Unsafe { - let diag = self.cx.span_diagnostic; - diag.span_fatal( - i.span, - "unsafe functions cannot be used for tests" - ).raise(); - } - if header.asyncness.is_async() { - let diag = self.cx.span_diagnostic; - diag.span_fatal( - i.span, - "async functions cannot be used for tests" - ).raise(); - } - } - _ => {}, - } + let mut item = i.into_inner(); + if is_test_case(&item) { + debug!("this is a test item"); - debug!("this is a test function"); let test = Test { - span: i.span, + span: item.span, path: self.cx.path.clone(), - bench: is_bench_fn(&self.cx, &i), - ignore: is_ignored(&i), - should_panic: should_panic(&i, &self.cx), - allow_fail: is_allowed_fail(&i), }; - self.cx.testfns.push(test); - self.tests.push(i.ident); + self.cx.test_cases.push(test); + self.tests.push(item.ident); } - let mut item = i.into_inner(); // We don't want to recurse into anything other than mods, since // mods or tests inside of functions will break things if let ast::ItemKind::Mod(module) = item.node { @@ -189,6 +162,8 @@ impl<'a> fold::Folder for TestHarnessGenerator<'a> { fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac } } +/// A folder used to remove any entry points (like fn main) because the harness +/// generator will provide its own struct EntryPointCleaner { // Current depth in the ast depth: usize, @@ -241,6 +216,10 @@ impl fold::Folder for EntryPointCleaner { fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac } } +/// Creates an item (specifically a module) that "pub use"s the tests passed in. +/// Each tested submodule will contain a similar reexport module that we will export +/// under the name of the original module. That is, `submod::__test_reexports` is +/// reexported like so `pub use submod::__test_reexports as submod`. fn mk_reexport_mod(cx: &mut TestCtxt, parent: ast::NodeId, tests: Vec, @@ -278,12 +257,14 @@ fn mk_reexport_mod(cx: &mut TestCtxt, (it, sym) } +/// Crawl over the crate, inserting test reexports and the test main function fn generate_test_harness(sess: &ParseSess, resolver: &mut dyn Resolver, reexport_test_harness_main: Option, krate: ast::Crate, sd: &errors::Handler, - features: &Features) -> ast::Crate { + features: &Features, + test_runner: Option) -> ast::Crate { // Remove the entry points let mut cleaner = EntryPointCleaner { depth: 0 }; let krate = cleaner.fold_crate(krate); @@ -297,19 +278,20 @@ fn generate_test_harness(sess: &ParseSess, span_diagnostic: sd, ext_cx: ExtCtxt::new(sess, econfig, resolver), path: Vec::new(), - testfns: Vec::new(), + test_cases: Vec::new(), reexport_test_harness_main, // NB: doesn't consider the value of `--crate-name` passed on the command line. is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false), toplevel_reexport: None, ctxt: SyntaxContext::empty().apply_mark(mark), features, + test_runner }; mark.set_expn_info(ExpnInfo { call_site: DUMMY_SP, def_site: None, - format: MacroAttribute(Symbol::intern("test")), + format: MacroAttribute(Symbol::intern("test_case")), allow_internal_unstable: true, allow_internal_unsafe: false, local_inner_macros: false, @@ -343,216 +325,57 @@ enum BadTestSignature { ShouldPanicOnlyWithNoArgs, } -fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool { - let has_test_attr = attr::contains_name(&i.attrs, "test"); - - fn has_test_signature(_cx: &TestCtxt, i: &ast::Item) -> HasTestSignature { - let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic"); - match i.node { - ast::ItemKind::Fn(ref decl, _, ref generics, _) => { - // If the termination trait is active, the compiler will check that the output - // type implements the `Termination` trait as `libtest` enforces that. - let has_output = match decl.output { - ast::FunctionRetTy::Default(..) => false, - ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false, - _ => true - }; - - if !decl.inputs.is_empty() { - return No(BadTestSignature::NoArgumentsAllowed); - } - - match (has_output, has_should_panic_attr) { - (true, true) => No(BadTestSignature::ShouldPanicOnlyWithNoArgs), - (true, false) => if !generics.params.is_empty() { - No(BadTestSignature::WrongTypeSignature) - } else { - Yes - }, - (false, _) => Yes - } - } - _ => No(BadTestSignature::NotEvenAFunction), - } - } - - let has_test_signature = if has_test_attr { - let diag = cx.span_diagnostic; - match has_test_signature(cx, i) { - Yes => true, - No(cause) => { - match cause { - BadTestSignature::NotEvenAFunction => - diag.span_err(i.span, "only functions may be used as tests"), - BadTestSignature::WrongTypeSignature => - diag.span_err(i.span, - "functions used as tests must have signature fn() -> ()"), - BadTestSignature::NoArgumentsAllowed => - diag.span_err(i.span, "functions used as tests can not have any arguments"), - BadTestSignature::ShouldPanicOnlyWithNoArgs => - diag.span_err(i.span, "functions using `#[should_panic]` must return `()`"), - } - false - } - } - } else { - false - }; - - has_test_attr && has_test_signature -} - -fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool { - let has_bench_attr = attr::contains_name(&i.attrs, "bench"); - - fn has_bench_signature(_cx: &TestCtxt, i: &ast::Item) -> bool { - match i.node { - ast::ItemKind::Fn(ref decl, _, _, _) => { - // NB: inadequate check, but we're running - // well before resolve, can't get too deep. - decl.inputs.len() == 1 - } - _ => false - } - } - - let has_bench_signature = has_bench_signature(cx, i); - - if has_bench_attr && !has_bench_signature { - let diag = cx.span_diagnostic; - - diag.span_err(i.span, "functions used as benches must have signature \ - `fn(&mut Bencher) -> impl Termination`"); - } - - has_bench_attr && has_bench_signature -} - -fn is_ignored(i: &ast::Item) -> bool { - attr::contains_name(&i.attrs, "ignore") -} - -fn is_allowed_fail(i: &ast::Item) -> bool { - attr::contains_name(&i.attrs, "allow_fail") -} - -fn should_panic(i: &ast::Item, cx: &TestCtxt) -> ShouldPanic { - match attr::find_by_name(&i.attrs, "should_panic") { - Some(attr) => { - let sd = cx.span_diagnostic; - if attr.is_value_str() { - sd.struct_span_warn( - attr.span(), - "attribute must be of the form: \ - `#[should_panic]` or \ - `#[should_panic(expected = \"error message\")]`" - ).note("Errors in this attribute were erroneously allowed \ - and will become a hard error in a future release.") - .emit(); - return ShouldPanic::Yes(None); - } - match attr.meta_item_list() { - // Handle #[should_panic] - None => ShouldPanic::Yes(None), - // Handle #[should_panic(expected = "foo")] - Some(list) => { - let msg = list.iter() - .find(|mi| mi.check_name("expected")) - .and_then(|mi| mi.meta_item()) - .and_then(|mi| mi.value_str()); - if list.len() != 1 || msg.is_none() { - sd.struct_span_warn( - attr.span(), - "argument must be of the form: \ - `expected = \"error message\"`" - ).note("Errors in this attribute were erroneously \ - allowed and will become a hard error in a \ - future release.").emit(); - ShouldPanic::Yes(None) - } else { - ShouldPanic::Yes(msg) - } - }, - } - } - None => ShouldPanic::No, - } -} - -/* - -We're going to be building a module that looks more or less like: - -mod __test { - extern crate test (name = "test", vers = "..."); - fn main() { - test::test_main_static(&::os::args()[], tests, test::Options::new()) - } - - static tests : &'static [test::TestDescAndFn] = &[ - ... the list of tests in the crate ... - ]; -} - -*/ - -fn mk_std(cx: &TestCtxt) -> P { - let id_test = Ident::from_str("test"); - let sp = ignored_span(cx, DUMMY_SP); - let (vi, vis, ident) = if cx.is_libtest { - (ast::ItemKind::Use(P(ast::UseTree { - span: DUMMY_SP, - prefix: path_node(vec![id_test]), - kind: ast::UseTreeKind::Simple(None, ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID), - })), - ast::VisibilityKind::Public, keywords::Invalid.ident()) - } else { - (ast::ItemKind::ExternCrate(None), ast::VisibilityKind::Inherited, id_test) - }; - P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident, - node: vi, - attrs: vec![], - vis: dummy_spanned(vis), - span: sp, - tokens: None, - }) -} - +/// Creates a function item for use as the main function of a test build. +/// This function will call the `test_runner` as specified by the crate attribute fn mk_main(cx: &mut TestCtxt) -> P { // Writing this out by hand with 'ignored_span': // pub fn main() { // #![main] - // use std::slice::AsSlice; - // test::test_main_static(::std::os::args().as_slice(), TESTS, test::Options::new()); + // test::test_main_static(::std::os::args().as_slice(), &[..tests]); // } - let sp = ignored_span(cx, DUMMY_SP); let ecx = &cx.ext_cx; - - // test::test_main_static - let test_main_path = - ecx.path(sp, vec![Ident::from_str("test"), Ident::from_str("test_main_static")]); + let test_id = ecx.ident_of("test").gensym(); // test::test_main_static(...) - let test_main_path_expr = ecx.expr_path(test_main_path); - let tests_ident_expr = ecx.expr_ident(sp, Ident::from_str("TESTS")); + let mut test_runner = cx.test_runner.clone().unwrap_or( + ecx.path(sp, vec![ + test_id, ecx.ident_of("test_main_static") + ])); + + test_runner.span = sp; + + let test_main_path_expr = ecx.expr_path(test_runner.clone()); let call_test_main = ecx.expr_call(sp, test_main_path_expr, - vec![tests_ident_expr]); + vec![mk_tests_slice(cx)]); let call_test_main = ecx.stmt_expr(call_test_main); + // #![main] let main_meta = ecx.meta_word(sp, Symbol::intern("main")); let main_attr = ecx.attribute(sp, main_meta); + + // extern crate test as test_gensym + let test_extern_stmt = ecx.stmt_item(sp, ecx.item(sp, + test_id, + vec![], + ast::ItemKind::ExternCrate(Some(Symbol::intern("test"))) + )); + // pub fn main() { ... } let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![])); - let main_body = ecx.block(sp, vec![call_test_main]); + let main_body = ecx.block(sp, vec![test_extern_stmt, call_test_main]); let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], ast::FunctionRetTy::Ty(main_ret_ty)), ast::FnHeader::default(), ast::Generics::default(), main_body); + + // Honor the reexport_test_harness_main attribute + let main_id = Ident::new( + cx.reexport_test_harness_main.unwrap_or(Symbol::gensym("main")), + sp); + P(ast::Item { - ident: Ident::from_str("main"), + ident: main_id, attrs: vec![main_attr], id: ast::DUMMY_NODE_ID, node: main, @@ -560,71 +383,7 @@ fn mk_main(cx: &mut TestCtxt) -> P { span: sp, tokens: None, }) -} - -fn mk_test_module(cx: &mut TestCtxt) -> (P, Option>) { - // Link to test crate - let import = mk_std(cx); - - // A constant vector of test descriptors. - let tests = mk_tests(cx); - - // The synthesized main function which will call the console test runner - // with our list of tests - let mainfn = mk_main(cx); - - let testmod = ast::Mod { - inner: DUMMY_SP, - items: vec![import, mainfn, tests], - }; - let item_ = ast::ItemKind::Mod(testmod); - let mod_ident = Ident::with_empty_ctxt(Symbol::gensym("__test")); - - let mut expander = cx.ext_cx.monotonic_expander(); - let item = expander.fold_item(P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident: mod_ident, - attrs: vec![], - node: item_, - vis: dummy_spanned(ast::VisibilityKind::Public), - span: DUMMY_SP, - tokens: None, - })).pop().unwrap(); - let reexport = cx.reexport_test_harness_main.map(|s| { - // building `use __test::main as ;` - let rename = Ident::with_empty_ctxt(s); - - let use_path = ast::UseTree { - span: DUMMY_SP, - prefix: path_node(vec![mod_ident, Ident::from_str("main")]), - kind: ast::UseTreeKind::Simple(Some(rename), ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID), - }; - - expander.fold_item(P(ast::Item { - id: ast::DUMMY_NODE_ID, - ident: keywords::Invalid.ident(), - attrs: vec![], - node: ast::ItemKind::Use(P(use_path)), - vis: dummy_spanned(ast::VisibilityKind::Inherited), - span: DUMMY_SP, - tokens: None, - })).pop().unwrap() - }); - debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item)); - - (item, reexport) -} - -fn nospan(t: T) -> source_map::Spanned { - source_map::Spanned { node: t, span: DUMMY_SP } -} - -fn path_node(ids: Vec) -> ast::Path { - ast::Path { - span: DUMMY_SP, - segments: ids.into_iter().map(|id| ast::PathSegment::from_ident(id)).collect(), - } } fn path_name_i(idents: &[Ident]) -> String { @@ -639,184 +398,49 @@ fn path_name_i(idents: &[Ident]) -> String { path_name } -fn mk_tests(cx: &TestCtxt) -> P { - // The vector of test_descs for this crate - let test_descs = mk_test_descs(cx); - - // FIXME #15962: should be using quote_item, but that stringifies - // __test_reexports, causing it to be reinterned, losing the - // gensym information. - let sp = ignored_span(cx, DUMMY_SP); - let ecx = &cx.ext_cx; - let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"), - ecx.ident_of("test"), - ecx.ident_of("TestDescAndFn")])); - let static_lt = ecx.lifetime(sp, keywords::StaticLifetime.ident()); - // &'static [self::test::TestDescAndFn] - let static_type = ecx.ty_rptr(sp, - ecx.ty(sp, ast::TyKind::Slice(struct_type)), - Some(static_lt), - ast::Mutability::Immutable); - // static TESTS: $static_type = &[...]; - ecx.item_const(sp, - ecx.ident_of("TESTS"), - static_type, - test_descs) -} - -fn mk_test_descs(cx: &TestCtxt) -> P { - debug!("building test vector from {} tests", cx.testfns.len()); - - P(ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprKind::AddrOf(ast::Mutability::Immutable, - P(ast::Expr { - id: ast::DUMMY_NODE_ID, - node: ast::ExprKind::Array(cx.testfns.iter().map(|test| { - mk_test_desc_and_fn_rec(cx, test) - }).collect()), - span: DUMMY_SP, - attrs: ThinVec::new(), - })), - span: DUMMY_SP, - attrs: ThinVec::new(), - }) +/// Creates a slice containing every test like so: +/// &[path::to::test1, path::to::test2] +fn mk_tests_slice(cx: &TestCtxt) -> P { + debug!("building test vector from {} tests", cx.test_cases.len()); + let ref ecx = cx.ext_cx; + + ecx.expr_vec_slice(DUMMY_SP, + cx.test_cases.iter().map(|test| { + ecx.expr_mut_addr_of(test.span, + ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path)))) + }).collect()) } -fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P { - // FIXME #15962: should be using quote_expr, but that stringifies - // __test_reexports, causing it to be reinterned, losing the - // gensym information. - - let span = ignored_span(cx, test.span); - let ecx = &cx.ext_cx; - let self_id = ecx.ident_of("self"); - let test_id = ecx.ident_of("test"); - - // creates self::test::$name - let test_path = |name| { - ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)]) - }; - // creates $name: $expr - let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr); - - // path to the #[test] function: "foo::bar::baz" - let path_string = path_name_i(&test.path[..]); - - debug!("encoding {}", path_string); - - let name_expr = ecx.expr_str(span, Symbol::intern(&path_string)); - - // self::test::StaticTestName($name_expr) - let name_expr = ecx.expr_call(span, - ecx.expr_path(test_path("StaticTestName")), - vec![name_expr]); - - let ignore_expr = ecx.expr_bool(span, test.ignore); - let should_panic_path = |name| { - ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)]) - }; - let fail_expr = match test.should_panic { - ShouldPanic::No => ecx.expr_path(should_panic_path("No")), - ShouldPanic::Yes(msg) => { - match msg { - Some(msg) => { - let msg = ecx.expr_str(span, msg); - let path = should_panic_path("YesWithMessage"); - ecx.expr_call(span, ecx.expr_path(path), vec![msg]) - } - None => ecx.expr_path(should_panic_path("Yes")), - } - } - }; - let allow_fail_expr = ecx.expr_bool(span, test.allow_fail); - - // self::test::TestDesc { ... } - let desc_expr = ecx.expr_struct( - span, - test_path("TestDesc"), - vec![field("name", name_expr), - field("ignore", ignore_expr), - field("should_panic", fail_expr), - field("allow_fail", allow_fail_expr)]); - +/// Creates a path from the top-level __test module to the test via __test_reexports +fn visible_path(cx: &TestCtxt, path: &[Ident]) -> Vec{ let mut visible_path = vec![]; - if cx.features.extern_absolute_paths { - visible_path.push(keywords::Crate.ident()); - } match cx.toplevel_reexport { Some(id) => visible_path.push(id), None => { - let diag = cx.span_diagnostic; - diag.bug("expected to find top-level re-export name, but found None"); + cx.span_diagnostic.bug("expected to find top-level re-export name, but found None"); } - }; - visible_path.extend_from_slice(&test.path[..]); - - // Rather than directly give the test function to the test - // harness, we create a wrapper like one of the following: - // - // || test::assert_test_result(real_function()) // for test - // |b| test::assert_test_result(real_function(b)) // for bench - // - // this will coerce into a fn pointer that is specialized to the - // actual return type of `real_function` (Typically `()`, but not always). - let fn_expr = { - // construct `real_function()` (this will be inserted into the overall expr) - let real_function_expr = ecx.expr_path(ecx.path_global(span, visible_path)); - // construct path `test::assert_test_result` - let assert_test_result = test_path("assert_test_result"); - if test.bench { - // construct `|b| {..}` - let b_ident = Ident::with_empty_ctxt(Symbol::gensym("b")); - let b_expr = ecx.expr_ident(span, b_ident); - ecx.lambda( - span, - vec![b_ident], - // construct `assert_test_result(..)` - ecx.expr_call( - span, - ecx.expr_path(assert_test_result), - vec![ - // construct `real_function(b)` - ecx.expr_call( - span, - real_function_expr, - vec![b_expr], - ) - ], - ), - ) - } else { - // construct `|| {..}` - ecx.lambda( - span, - vec![], - // construct `assert_test_result(..)` - ecx.expr_call( - span, - ecx.expr_path(assert_test_result), - vec![ - // construct `real_function()` - ecx.expr_call( - span, - real_function_expr, - vec![], - ) - ], - ), - ) - } - }; - - let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" }; + } + visible_path.extend_from_slice(path); + visible_path +} - // self::test::$variant_name($fn_expr) - let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]); +fn is_test_case(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "test_case") +} - // self::test::TestDescAndFn { ... } - ecx.expr_struct(span, - test_path("TestDescAndFn"), - vec![field("desc", desc_expr), - field("testfn", testfn_expr)]) +fn get_test_runner(sd: &errors::Handler, krate: &ast::Crate) -> Option { + if let Some(test_attr) = attr::find_by_name(&krate.attrs, "test_runner") { + if let Some(meta_list) = test_attr.meta_item_list() { + if meta_list.len() != 1 { + sd.span_fatal(test_attr.span(), + "#![test_runner(..)] accepts exactly 1 argument").raise() + } + Some(meta_list[0].word().as_ref().unwrap().ident.clone()) + } else { + sd.span_fatal(test_attr.span(), + "test_runner must be of the form #[test_runner(..)]").raise() + } + } else { + None + } } diff --git a/src/libsyntax_ext/Cargo.toml b/src/libsyntax_ext/Cargo.toml index 1676757d9b89d..02f1fe9e93587 100644 --- a/src/libsyntax_ext/Cargo.toml +++ b/src/libsyntax_ext/Cargo.toml @@ -15,4 +15,5 @@ rustc_errors = { path = "../librustc_errors" } syntax = { path = "../libsyntax" } syntax_pos = { path = "../libsyntax_pos" } rustc_data_structures = { path = "../librustc_data_structures" } -rustc_target = { path = "../librustc_target" } \ No newline at end of file +rustc_target = { path = "../librustc_target" } +log = "0.4" diff --git a/src/libsyntax_ext/lib.rs b/src/libsyntax_ext/lib.rs index 1ba4ab474258c..940add7638770 100644 --- a/src/libsyntax_ext/lib.rs +++ b/src/libsyntax_ext/lib.rs @@ -18,7 +18,7 @@ #![feature(decl_macro)] #![cfg_attr(not(stage0), feature(nll))] #![feature(str_escape)] - +#![feature(quote)] #![feature(rustc_diagnostic_macros)] extern crate fmt_macros; @@ -29,6 +29,7 @@ extern crate proc_macro; extern crate rustc_data_structures; extern crate rustc_errors as errors; extern crate rustc_target; +#[macro_use] extern crate log; mod diagnostics; @@ -48,6 +49,7 @@ mod format_foreign; mod global_asm; mod log_syntax; mod trace_macros; +mod test; pub mod proc_macro_registrar; @@ -56,7 +58,7 @@ pub mod proc_macro_impl; use rustc_data_structures::sync::Lrc; use syntax::ast; -use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension}; +use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension, MultiModifier}; use syntax::ext::hygiene; use syntax::symbol::Symbol; @@ -127,6 +129,9 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver, assert: assert::expand_assert, } + register(Symbol::intern("test"), MultiModifier(Box::new(test::expand_test))); + register(Symbol::intern("bench"), MultiModifier(Box::new(test::expand_bench))); + // format_args uses `unstable` things internally. register(Symbol::intern("format_args"), NormalTT { diff --git a/src/libsyntax_ext/test.rs b/src/libsyntax_ext/test.rs new file mode 100644 index 0000000000000..e92eb01eff5e7 --- /dev/null +++ b/src/libsyntax_ext/test.rs @@ -0,0 +1,321 @@ +// Copyright 2013 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. + +/// The expansion from a test function to the appropriate test struct for libtest +/// Ideally, this code would be in libtest but for efficiency and error messages it lives here. + +use syntax::ext::base::*; +use syntax::ext::build::AstBuilder; +use syntax::ext::hygiene::{self, Mark, SyntaxContext}; +use syntax::attr; +use syntax::ast; +use syntax::print::pprust; +use syntax::symbol::Symbol; +use syntax_pos::{DUMMY_SP, Span}; +use syntax::source_map::{ExpnInfo, MacroAttribute}; +use std::iter; + +pub fn expand_test( + cx: &mut ExtCtxt, + attr_sp: Span, + _meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + expand_test_or_bench(cx, attr_sp, item, false) +} + +pub fn expand_bench( + cx: &mut ExtCtxt, + attr_sp: Span, + _meta_item: &ast::MetaItem, + item: Annotatable, +) -> Vec { + expand_test_or_bench(cx, attr_sp, item, true) +} + +pub fn expand_test_or_bench( + cx: &mut ExtCtxt, + attr_sp: Span, + item: Annotatable, + is_bench: bool +) -> Vec { + // If we're not in test configuration, remove the annotated item + if !cx.ecfg.should_test { return vec![]; } + + let item = + if let Annotatable::Item(i) = item { i } + else { + cx.parse_sess.span_diagnostic.span_fatal(item.span(), + "#[test] attribute is only allowed on fn items").raise(); + }; + + // has_*_signature will report any errors in the type so compilation + // will fail. We shouldn't try to expand in this case because the errors + // would be spurious. + if (!is_bench && !has_test_signature(cx, &item)) || + (is_bench && !has_bench_signature(cx, &item)) { + return vec![Annotatable::Item(item)]; + } + + let (sp, attr_sp) = { + let mark = Mark::fresh(Mark::root()); + mark.set_expn_info(ExpnInfo { + call_site: DUMMY_SP, + def_site: None, + format: MacroAttribute(Symbol::intern("test")), + allow_internal_unstable: true, + allow_internal_unsafe: false, + local_inner_macros: false, + edition: hygiene::default_edition(), + }); + (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)), + attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark))) + }; + + // Gensym "test" so we can extern crate without conflicting with any local names + let test_id = cx.ident_of("test").gensym(); + + // creates test::$name + let test_path = |name| { + cx.path(sp, vec![test_id, cx.ident_of(name)]) + }; + + // creates test::$name + let should_panic_path = |name| { + cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)]) + }; + + // creates $name: $expr + let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr); + + let test_fn = if is_bench { + // A simple ident for a lambda + let b = cx.ident_of("b"); + + cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![ + // |b| self::test::assert_test_result( + cx.lambda1(sp, + cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![ + // super::$test_fn(b) + cx.expr_call(sp, + cx.expr_path(cx.path(sp, vec![item.ident])), + vec![cx.expr_ident(sp, b)]) + ]), + b + ) + // ) + ]) + } else { + cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![ + // || { + cx.lambda0(sp, + // test::assert_test_result( + cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![ + // $test_fn() + cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![]) + // ) + ]) + // } + ) + // ) + ]) + }; + + let mut test_const = cx.item(sp, item.ident.gensym(), + // #[test_case] + vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))], + // const $ident: test::TestDescAndFn = + ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))), + // test::TestDescAndFn { + cx.expr_struct(sp, test_path("TestDescAndFn"), vec![ + // desc: test::TestDesc { + field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![ + // name: "path::to::test" + field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")), + vec![ + cx.expr_str(sp, Symbol::intern(&item_path( + &cx.current_expansion.module.mod_path, + &item.ident + ))) + ])), + // ignore: true | false + field("ignore", cx.expr_bool(sp, should_ignore(&item))), + // allow_fail: true | false + field("allow_fail", cx.expr_bool(sp, should_fail(&item))), + // should_panic: ... + field("should_panic", match should_panic(cx, &item) { + // test::ShouldPanic::No + ShouldPanic::No => cx.expr_path(should_panic_path("No")), + // test::ShouldPanic::Yes + ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")), + // test::ShouldPanic::YesWithMessage("...") + ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp, + cx.expr_path(should_panic_path("YesWithMessage")), + vec![cx.expr_str(sp, sym)]), + }), + // }, + ])), + // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...) + field("testfn", test_fn) + // } + ]) + // } + )); + test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc}); + + // extern crate test as test_gensym + let test_extern = cx.item(sp, + test_id, + vec![], + ast::ItemKind::ExternCrate(Some(Symbol::intern("test"))) + ); + + debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const)); + + vec![ + // Access to libtest under a gensymed name + Annotatable::Item(test_extern), + // The generated test case + Annotatable::Item(test_const), + // The original item + Annotatable::Item(item) + ] +} + +fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String { + mod_path.iter().chain(iter::once(item_ident)) + .map(|x| x.to_string()).collect::>().join("::") +} + +enum ShouldPanic { + No, + Yes(Option), +} + +fn should_ignore(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "ignore") +} + +fn should_fail(i: &ast::Item) -> bool { + attr::contains_name(&i.attrs, "allow_fail") +} + +fn should_panic(cx: &ExtCtxt, i: &ast::Item) -> ShouldPanic { + match attr::find_by_name(&i.attrs, "should_panic") { + Some(attr) => { + let ref sd = cx.parse_sess.span_diagnostic; + if attr.is_value_str() { + sd.struct_span_warn( + attr.span(), + "attribute must be of the form: \ + `#[should_panic]` or \ + `#[should_panic(expected = \"error message\")]`" + ).note("Errors in this attribute were erroneously allowed \ + and will become a hard error in a future release.") + .emit(); + return ShouldPanic::Yes(None); + } + match attr.meta_item_list() { + // Handle #[should_panic] + None => ShouldPanic::Yes(None), + // Handle #[should_panic(expected = "foo")] + Some(list) => { + let msg = list.iter() + .find(|mi| mi.check_name("expected")) + .and_then(|mi| mi.meta_item()) + .and_then(|mi| mi.value_str()); + if list.len() != 1 || msg.is_none() { + sd.struct_span_warn( + attr.span(), + "argument must be of the form: \ + `expected = \"error message\"`" + ).note("Errors in this attribute were erroneously \ + allowed and will become a hard error in a \ + future release.").emit(); + ShouldPanic::Yes(None) + } else { + ShouldPanic::Yes(msg) + } + }, + } + } + None => ShouldPanic::No, + } +} + +fn has_test_signature(cx: &ExtCtxt, i: &ast::Item) -> bool { + let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic"); + let ref sd = cx.parse_sess.span_diagnostic; + if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node { + if header.unsafety == ast::Unsafety::Unsafe { + sd.span_err( + i.span, + "unsafe functions cannot be used for tests" + ); + return false + } + if header.asyncness.is_async() { + sd.span_err( + i.span, + "async functions cannot be used for tests" + ); + return false + } + + + // If the termination trait is active, the compiler will check that the output + // type implements the `Termination` trait as `libtest` enforces that. + let has_output = match decl.output { + ast::FunctionRetTy::Default(..) => false, + ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false, + _ => true + }; + + if !decl.inputs.is_empty() { + sd.span_err(i.span, "functions used as tests can not have any arguments"); + return false; + } + + match (has_output, has_should_panic_attr) { + (true, true) => { + sd.span_err(i.span, "functions using `#[should_panic]` must return `()`"); + false + }, + (true, false) => if !generics.params.is_empty() { + sd.span_err(i.span, + "functions used as tests must have signature fn() -> ()"); + false + } else { + true + }, + (false, _) => true + } + } else { + sd.span_err(i.span, "only functions may be used as tests"); + false + } +} + +fn has_bench_signature(cx: &ExtCtxt, i: &ast::Item) -> bool { + let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node { + // NB: inadequate check, but we're running + // well before resolve, can't get too deep. + decl.inputs.len() == 1 + } else { + false + }; + + if !has_sig { + cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \ + signature `fn(&mut Bencher) -> impl Termination`"); + } + + has_sig +} diff --git a/src/libtest/lib.rs b/src/libtest/lib.rs index 060ea1ea9b132..f67abeff0bd43 100644 --- a/src/libtest/lib.rs +++ b/src/libtest/lib.rs @@ -40,6 +40,7 @@ #![feature(panic_unwind)] #![feature(staged_api)] #![feature(termination_trait_lib)] +#![feature(test)] extern crate getopts; #[cfg(any(unix, target_os = "cloudabi"))] @@ -301,7 +302,7 @@ pub fn test_main(args: &[String], tests: Vec, options: Options) { // a Vec is used in order to effect ownership-transfer // semantics into parallel test runners, which in turn requires a Vec<> // rather than a &[]. -pub fn test_main_static(tests: &[TestDescAndFn]) { +pub fn test_main_static(tests: &[&mut TestDescAndFn]) { let args = env::args().collect::>(); let owned_tests = tests .iter() diff --git a/src/libtest/stats.rs b/src/libtest/stats.rs index ddb5dcf2a1cd3..9a8749712c355 100644 --- a/src/libtest/stats.rs +++ b/src/libtest/stats.rs @@ -907,7 +907,8 @@ mod tests { #[cfg(test)] mod bench { - use Bencher; + extern crate test; + use self::test::Bencher; use stats::Stats; #[bench] diff --git a/src/test/incremental/issue-49595/issue_49595.rs b/src/test/incremental/issue-49595/issue_49595.rs index 134f114e6acca..7067e7250728f 100644 --- a/src/test/incremental/issue-49595/issue_49595.rs +++ b/src/test/incremental/issue-49595/issue_49595.rs @@ -15,12 +15,11 @@ #![feature(rustc_attrs)] #![crate_type = "rlib"] -#![rustc_partition_codegened(module="issue_49595-__test", cfg="cfail2")] +#![rustc_partition_codegened(module="issue_49595-tests", cfg="cfail2")] #![rustc_partition_codegened(module="issue_49595-lit_test", cfg="cfail3")] mod tests { - #[cfg_attr(not(cfail1), ignore)] - #[test] + #[cfg_attr(not(cfail1), test)] fn test() { } } diff --git a/src/test/run-fail/test-panic.rs b/src/test/run-fail/test-panic.rs index bb6f4abe1fc96..42e8455a1665a 100644 --- a/src/test/run-fail/test-panic.rs +++ b/src/test/run-fail/test-panic.rs @@ -9,7 +9,7 @@ // except according to those terms. // check-stdout -// error-pattern:thread 'test_foo' panicked at +// error-pattern:thread 'test_panic::test_foo' panicked at // compile-flags: --test // ignore-emscripten diff --git a/src/test/run-fail/test-should-fail-bad-message.rs b/src/test/run-fail/test-should-fail-bad-message.rs index eac9813f180ae..eb5f7a45100b5 100644 --- a/src/test/run-fail/test-should-fail-bad-message.rs +++ b/src/test/run-fail/test-should-fail-bad-message.rs @@ -9,7 +9,7 @@ // except according to those terms. // check-stdout -// error-pattern:thread 'test_foo' panicked at +// error-pattern:thread 'test_should_fail_bad_message::test_foo' panicked at // compile-flags: --test // ignore-emscripten diff --git a/src/test/run-make-fulldeps/libtest-json/output.json b/src/test/run-make-fulldeps/libtest-json/output.json index 235f8cd7c7257..4fe6d8e487d49 100644 --- a/src/test/run-make-fulldeps/libtest-json/output.json +++ b/src/test/run-make-fulldeps/libtest-json/output.json @@ -1,10 +1,10 @@ { "type": "suite", "event": "started", "test_count": "4" } -{ "type": "test", "event": "started", "name": "a" } -{ "type": "test", "name": "a", "event": "ok" } -{ "type": "test", "event": "started", "name": "b" } -{ "type": "test", "name": "b", "event": "failed", "stdout": "thread 'b' panicked at 'assertion failed: false', f.rs:18:5\nnote: Run with `RUST_BACKTRACE=1` for a backtrace.\n" } -{ "type": "test", "event": "started", "name": "c" } -{ "type": "test", "name": "c", "event": "ok" } -{ "type": "test", "event": "started", "name": "d" } -{ "type": "test", "name": "d", "event": "ignored" } +{ "type": "test", "event": "started", "name": "f::a" } +{ "type": "test", "name": "f::a", "event": "ok" } +{ "type": "test", "event": "started", "name": "f::b" } +{ "type": "test", "name": "f::b", "event": "failed", "stdout": "thread 'f::b' panicked at 'assertion failed: false', f.rs:18:5\nnote: Run with `RUST_BACKTRACE=1` for a backtrace.\n" } +{ "type": "test", "event": "started", "name": "f::c" } +{ "type": "test", "name": "f::c", "event": "ok" } +{ "type": "test", "event": "started", "name": "f::d" } +{ "type": "test", "name": "f::d", "event": "ignored" } { "type": "suite", "event": "failed", "passed": 2, "failed": 1, "allowed_fail": 0, "ignored": 1, "measured": 0, "filtered_out": "0" } diff --git a/src/test/ui/cfg-non-opt-expr.rs b/src/test/ui/cfg-non-opt-expr.rs index a4b24fa8b4bf6..bd0a5c66b3ee2 100644 --- a/src/test/ui/cfg-non-opt-expr.rs +++ b/src/test/ui/cfg-non-opt-expr.rs @@ -9,6 +9,7 @@ // except according to those terms. #![feature(stmt_expr_attributes)] +#![feature(custom_test_frameworks)] fn main() { let _ = #[cfg(unset)] (); @@ -17,6 +18,6 @@ fn main() { //~^ ERROR removing an expression is not supported in this position let _ = [1, 2, 3][#[cfg(unset)] 1]; //~^ ERROR removing an expression is not supported in this position - let _ = #[test] (); + let _ = #[test_case] (); //~^ ERROR removing an expression is not supported in this position } diff --git a/src/test/ui/cfg-non-opt-expr.stderr b/src/test/ui/cfg-non-opt-expr.stderr index 0511c5755462e..8c5d8900f8b2e 100644 --- a/src/test/ui/cfg-non-opt-expr.stderr +++ b/src/test/ui/cfg-non-opt-expr.stderr @@ -1,26 +1,26 @@ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:14:13 + --> $DIR/cfg-non-opt-expr.rs:15:13 | LL | let _ = #[cfg(unset)] (); | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:16:21 + --> $DIR/cfg-non-opt-expr.rs:17:21 | LL | let _ = 1 + 2 + #[cfg(unset)] 3; | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:18:23 + --> $DIR/cfg-non-opt-expr.rs:19:23 | LL | let _ = [1, 2, 3][#[cfg(unset)] 1]; | ^^^^^^^^^^^^^ error: removing an expression is not supported in this position - --> $DIR/cfg-non-opt-expr.rs:20:13 + --> $DIR/cfg-non-opt-expr.rs:21:13 | -LL | let _ = #[test] (); - | ^^^^^^^ +LL | let _ = #[test_case] (); + | ^^^^^^^^^^^^ error: aborting due to 4 previous errors diff --git a/src/test/ui/custom-test-frameworks-simple.rs b/src/test/ui/custom-test-frameworks-simple.rs new file mode 100644 index 0000000000000..39a4dc569fa62 --- /dev/null +++ b/src/test/ui/custom-test-frameworks-simple.rs @@ -0,0 +1,32 @@ +// Copyright 2017 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. + +// compile-flags: --test +// run-pass + +#![feature(custom_test_frameworks)] +#![test_runner(crate::foo_runner)] + +#[cfg(test)] +fn foo_runner(ts: &[&Fn(usize)->()]) { + for (i, t) in ts.iter().enumerate() { + t(i); + } +} + +#[test_case] +fn test1(i: usize) { + println!("Hi #{}", i); +} + +#[test_case] +fn test2(i: usize) { + println!("Hey #{}", i); +} diff --git a/src/test/ui/feature-gate-custom_test_frameworks.rs b/src/test/ui/feature-gate-custom_test_frameworks.rs new file mode 100644 index 0000000000000..e8d1524996fa1 --- /dev/null +++ b/src/test/ui/feature-gate-custom_test_frameworks.rs @@ -0,0 +1,13 @@ +// Copyright 2017 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. + +#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature + +fn main() {} diff --git a/src/test/ui/feature-gate-custom_test_frameworks.stderr b/src/test/ui/feature-gate-custom_test_frameworks.stderr new file mode 100644 index 0000000000000..cd04f32697bf5 --- /dev/null +++ b/src/test/ui/feature-gate-custom_test_frameworks.stderr @@ -0,0 +1,11 @@ +error[E0658]: Custom Test Frameworks is an unstable feature (see issue #50297) + --> $DIR/feature-gate-custom_test_frameworks.rs:11:1 + | +LL | #![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: add #![feature(custom_test_frameworks)] to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/inaccessible-test-modules.stderr b/src/test/ui/inaccessible-test-modules.stderr index ce8eaf590275f..5b964c1a14b99 100644 --- a/src/test/ui/inaccessible-test-modules.stderr +++ b/src/test/ui/inaccessible-test-modules.stderr @@ -2,7 +2,7 @@ error[E0432]: unresolved import `__test` --> $DIR/inaccessible-test-modules.rs:15:5 | LL | use __test as x; //~ ERROR unresolved import `__test` - | ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `__test`? + | ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `test`? error[E0432]: unresolved import `__test_reexports` --> $DIR/inaccessible-test-modules.rs:16:5 diff --git a/src/test/ui/issues/issue-11692-2.rs b/src/test/ui/issues/issue-11692-2.rs index acac2d151fe9a..d1f5712afb6c9 100644 --- a/src/test/ui/issues/issue-11692-2.rs +++ b/src/test/ui/issues/issue-11692-2.rs @@ -10,5 +10,5 @@ fn main() { concat!(test!()); - //~^ ERROR cannot find macro `test!` in this scope + //~^ error: `test` can only be used in attributes } diff --git a/src/test/ui/issues/issue-11692-2.stderr b/src/test/ui/issues/issue-11692-2.stderr index 51d6041e92220..6c21287bed304 100644 --- a/src/test/ui/issues/issue-11692-2.stderr +++ b/src/test/ui/issues/issue-11692-2.stderr @@ -1,4 +1,4 @@ -error: cannot find macro `test!` in this scope +error: `test` can only be used in attributes --> $DIR/issue-11692-2.rs:12:13 | LL | concat!(test!()); diff --git a/src/test/ui/issues/issue-12997-2.stderr b/src/test/ui/issues/issue-12997-2.stderr index 3030ee4779b4b..853a2a0f1b4f1 100644 --- a/src/test/ui/issues/issue-12997-2.stderr +++ b/src/test/ui/issues/issue-12997-2.stderr @@ -5,7 +5,7 @@ LL | fn bar(x: isize) { } | ^^^^^^^^^^^^^^^^^^^^ expected isize, found mutable reference | = note: expected type `isize` - found type `&mut __test::test::Bencher` + found type `&mut test::Bencher` error: aborting due to previous error diff --git a/src/test/ui/lint/test-inner-fn.rs b/src/test/ui/lint/test-inner-fn.rs index 4304c96197f96..a7727c69e4c0c 100644 --- a/src/test/ui/lint/test-inner-fn.rs +++ b/src/test/ui/lint/test-inner-fn.rs @@ -8,11 +8,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -// compile-flags: --test -D unnameable_test_functions +// compile-flags: --test -D unnameable_test_items #[test] fn foo() { - #[test] //~ ERROR cannot test inner function [unnameable_test_functions] + #[test] //~ ERROR cannot test inner items [unnameable_test_items] fn bar() {} bar(); } @@ -20,7 +20,7 @@ fn foo() { mod x { #[test] fn foo() { - #[test] //~ ERROR cannot test inner function [unnameable_test_functions] + #[test] //~ ERROR cannot test inner items [unnameable_test_items] fn bar() {} bar(); } diff --git a/src/test/ui/lint/test-inner-fn.stderr b/src/test/ui/lint/test-inner-fn.stderr index 37f0c161036ee..182fb31a9aa37 100644 --- a/src/test/ui/lint/test-inner-fn.stderr +++ b/src/test/ui/lint/test-inner-fn.stderr @@ -1,15 +1,15 @@ -error: cannot test inner function +error: cannot test inner items --> $DIR/test-inner-fn.rs:15:5 | -LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions] +LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items] | ^^^^^^^ | - = note: requested on the command line with `-D unnameable-test-functions` + = note: requested on the command line with `-D unnameable-test-items` -error: cannot test inner function +error: cannot test inner items --> $DIR/test-inner-fn.rs:23:9 | -LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions] +LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items] | ^^^^^^^ error: aborting due to 2 previous errors diff --git a/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr b/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr index 0972a0994fc0d..0e95c053ce4cf 100644 --- a/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr +++ b/src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr @@ -7,7 +7,7 @@ LL | | } | |_^ `main` can only return types that implement `std::process::Termination` | = help: the trait `std::process::Termination` is not implemented for `std::result::Result` - = note: required by `__test::test::assert_test_result` + = note: required by `test::assert_test_result` error: aborting due to previous error