diff options
| author | Thomas Lively <7121787+tlively@users.noreply.github.com> | 2018-09-05 21:40:39 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-09-05 21:40:39 -0700 |
| commit | 3dab33225a8c74dcf1f6a4f6161a10b60d5944e9 (patch) | |
| tree | 24f2e486873ff3f12493265327d999767cf21599 /src/libsyntax | |
| parent | 482346ce75ff112bfbe991d37fb66057cb80c804 (diff) | |
| parent | 27e5457f3fa6c6e322e05352f0109f2cd511396c (diff) | |
| download | rust-3dab33225a8c74dcf1f6a4f6161a10b60d5944e9.tar.gz rust-3dab33225a8c74dcf1f6a4f6161a10b60d5944e9.zip | |
Merge branch 'master' into fix-submodules
Diffstat (limited to 'src/libsyntax')
| -rw-r--r-- | src/libsyntax/ast.rs | 2 | ||||
| -rw-r--r-- | src/libsyntax/config.rs | 15 | ||||
| -rw-r--r-- | src/libsyntax/ext/base.rs | 3 | ||||
| -rw-r--r-- | src/libsyntax/ext/expand.rs | 80 | ||||
| -rw-r--r-- | src/libsyntax/feature_gate.rs | 18 | ||||
| -rw-r--r-- | src/libsyntax/parse/parser.rs | 1 | ||||
| -rw-r--r-- | src/libsyntax/test.rs | 580 |
7 files changed, 132 insertions, 567 deletions
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 72f1791ef7c..9851749be37 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -1587,7 +1587,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 0e52434ec01..5233267e3a9 100644 --- a/src/libsyntax/config.rs +++ b/src/libsyntax/config.rs @@ -21,18 +21,16 @@ use ptr::P; /// A folder that strips out items that do not belong in the current configuration. pub struct StripUnconfigured<'a> { - pub should_test: bool, pub sess: &'a ParseSess, pub features: Option<&'a Features>, } // `cfg_attr`-process the crate's attributes and compute the crate's features. -pub fn features(mut krate: ast::Crate, sess: &ParseSess, should_test: bool, edition: Edition) +pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition) -> (ast::Crate, Features) { let features; { let mut strip_unconfigured = StripUnconfigured { - should_test, sess, features: None, }; @@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> { // Determine if a node with the given attributes should be included in this configuration. 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) { - return false; - } - let mis = if !is_cfg(attr) { return true; } else if let Some(mis) = attr.meta_item_list() { @@ -249,7 +242,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)) { let msg = "removing an expression is not supported in this position"; self.sess.span_diagnostic.span_err(attr.span, msg); } @@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> { 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") -} diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index e8a68b6d767..0e059bc4a6c 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -721,6 +721,7 @@ pub trait Resolver { fn visit_ast_fragment_with_placeholders(&mut self, mark: Mark, fragment: &AstFragment, derives: &[Mark]); fn add_builtin(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>); + fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>); fn resolve_imports(&mut self); // Resolves attribute and derive legacy macros from `#![plugin(..)]`. @@ -729,6 +730,7 @@ pub trait Resolver { fn resolve_macro_invocation(&mut self, invoc: &Invocation, scope: Mark, force: bool) -> Result<Option<Lrc<SyntaxExtension>>, Determinacy>; + fn resolve_macro_path(&mut self, path: &ast::Path, kind: MacroKind, scope: Mark, derives_in_scope: &[ast::Path], force: bool) -> Result<Lrc<SyntaxExtension>, Determinacy>; @@ -759,6 +761,7 @@ impl Resolver for DummyResolver { fn visit_ast_fragment_with_placeholders(&mut self, _invoc: Mark, _fragment: &AstFragment, _derives: &[Mark]) {} fn add_builtin(&mut self, _ident: ast::Ident, _ext: Lrc<SyntaxExtension>) {} + fn add_unshadowable_attr(&mut self, _ident: ast::Ident, _ext: Lrc<SyntaxExtension>) {} fn resolve_imports(&mut self) {} fn find_legacy_attr_invoc(&mut self, _attrs: &mut Vec<Attribute>, _allow_derive: bool) diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 6e38f820586..3bb19121ee3 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}; @@ -37,7 +36,6 @@ use visit::{self, Visitor}; use rustc_data_structures::fx::FxHashMap; use std::fs::File; use std::io::Read; -use std::iter::FromIterator; use std::{iter, mem}; use std::rc::Rc; use std::path::PathBuf; @@ -452,14 +450,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> { let (fragment_with_placeholders, invocations) = { let mut collector = InvocationCollector { cfg: StripUnconfigured { - should_test: self.cx.ecfg.should_test, sess: self.cx.parse_sess, features: self.cx.ecfg.features, }, cx: self.cx, invocations: Vec::new(), monotonic: self.monotonic, - tests_nameable: true, }; (fragment.fold_with(&mut collector), collector.invocations) }; @@ -477,7 +473,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> { fn fully_configure(&mut self, item: Annotatable) -> Annotatable { let mut cfg = StripUnconfigured { - should_test: self.cx.ecfg.should_test, sess: self.cx.parse_sess, features: self.cx.ecfg.features, }; @@ -1049,11 +1044,6 @@ struct InvocationCollector<'a, 'b: 'a> { cfg: StripUnconfigured<'a>, invocations: Vec<Invocation>, monotonic: bool, - - /// Test functions need to be nameable. Tests inside functions or in other - /// unnameable locations need to be ignored. `tests_nameable` tracks whether - /// any test functions found in the current context would be nameable. - tests_nameable: bool, } impl<'a, 'b> InvocationCollector<'a, 'b> { @@ -1071,20 +1061,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { placeholder(fragment_kind, NodeId::placeholder_from_mark(mark)) } - /// Folds the item allowing tests to be expanded because they are still nameable. - /// This should probably only be called with module items - fn fold_nameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> { - fold::noop_fold_item(item, self) - } - - /// Folds the item but doesn't allow tests to occur within it - fn fold_unnameable(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> { - let was_nameable = mem::replace(&mut self.tests_nameable, false); - let items = fold::noop_fold_item(item, self); - self.tests_nameable = was_nameable; - items - } - fn collect_bang(&mut self, mac: ast::Mac, span: Span, kind: AstFragmentKind) -> AstFragment { self.collect(kind, InvocationKind::Bang { mac: mac, ident: None, span: span }) } @@ -1299,7 +1275,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { fn fold_item(&mut self, item: P<ast::Item>) -> OneVector<P<ast::Item>> { let item = configure!(self, item); - let (attr, traits, mut item) = self.classify_item(item); + let (attr, traits, item) = self.classify_item(item); if attr.is_some() || !traits.is_empty() { let item = Annotatable::Item(item); return self.collect_attr(attr, traits, item, AstFragmentKind::Items).make_items(); @@ -1321,7 +1297,7 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { } ast::ItemKind::Mod(ast::Mod { inner, .. }) => { if item.ident == keywords::Invalid.ident() { - return self.fold_nameable(item); + return noop_fold_item(item, self); } let orig_directory_ownership = self.cx.current_expansion.directory_ownership; @@ -1361,58 +1337,13 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> { let orig_module = mem::replace(&mut self.cx.current_expansion.module, Rc::new(module)); - let result = self.fold_nameable(item); + let result = noop_fold_item(item, self); self.cx.current_expansion.module = orig_module; self.cx.current_expansion.directory_ownership = orig_directory_ownership; result } - // Ensure that test functions are accessible from the test harness. - // #[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(); - - // Publicize the item under gensymed name to avoid pollution - 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::from_iter( - self.fold_unnameable(item).into_iter() - .chain(self.fold_unnameable(use_item))) - } else { - self.fold_unnameable(item) - } - } - _ => self.fold_unnameable(item), + _ => noop_fold_item(item, self), } } @@ -1637,6 +1568,7 @@ impl<'feat> ExpansionConfig<'feat> { feature_tests! { fn enable_quotes = quote, fn enable_asm = asm, + fn enable_custom_test_frameworks = custom_test_frameworks, fn enable_global_asm = global_asm, fn enable_log_syntax = log_syntax, fn enable_concat_idents = concat_idents, diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 14781dd8e24..e3ea3563d85 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -515,6 +515,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! ( @@ -765,8 +769,6 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG ("cfg_attr", Normal, Ungated), ("main", Normal, Ungated), ("start", Normal, Ungated), - ("test", Normal, Ungated), - ("bench", Normal, Ungated), ("repr", Normal, Ungated), ("path", Normal, Ungated), ("abi", Normal, Ungated), @@ -959,6 +961,11 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG attribute is just used for rustc unit \ tests and will never be stable", cfg_fn!(rustc_attrs))), + ("rustc_test_marker", Normal, Gated(Stability::Unstable, + "rustc_attrs", + "the `#[rustc_test_marker]` attribute \ + is used internally to track tests", + cfg_fn!(rustc_attrs))), // RFC #2094 ("nll", Whitelisted, Gated(Stability::Unstable, @@ -1156,6 +1163,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", + EXPLAIN_CUSTOM_TEST_FRAMEWORKS, + cfg_fn!(custom_test_frameworks))), ]; // cfg(...)'s that are feature gated @@ -1372,6 +1383,9 @@ pub const EXPLAIN_ASM: &'static str = pub const EXPLAIN_GLOBAL_ASM: &'static str = "`global_asm!` is not stable enough for use and is subject to change"; +pub const EXPLAIN_CUSTOM_TEST_FRAMEWORKS: &'static str = + "custom test frameworks are an unstable feature"; + pub const EXPLAIN_LOG_SYNTAX: &'static str = "`log_syntax!` is not stable enough for use and is subject to change"; diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 3862877c3d9..5f80af77f49 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -6272,7 +6272,6 @@ impl<'a> Parser<'a> { let (in_cfg, outer_attrs) = { let mut strip_unconfigured = ::config::StripUnconfigured { sess: self.sess, - should_test: false, // irrelevant features: None, // don't perform gated feature checking }; let outer_attrs = strip_unconfigured.process_cfg_attrs(outer_attrs.to_owned()); diff --git a/src/libsyntax/test.rs b/src/libsyntax/test.rs index 49ab0c2256e..ab67736c389 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}; @@ -43,29 +43,21 @@ use symbol::{self, Symbol, keywords}; use ThinVec; use rustc_data_structures::small_vec::ExpectOne; -enum ShouldPanic { - No, - Yes(Option<Symbol>), -} - struct Test { span: Span, - path: Vec<Ident> , - bench: bool, - ignore: bool, - should_panic: ShouldPanic, - allow_fail: bool, + path: Vec<Ident>, } struct TestCtxt<'a> { span_diagnostic: &'a errors::Handler, path: Vec<Ident>, ext_cx: ExtCtxt<'a>, - testfns: Vec<Test>, + test_cases: Vec<Test>, reexport_test_harness_main: Option<Symbol>, is_libtest: bool, ctxt: SyntaxContext, features: &'a Features, + test_runner: Option<ast::Path>, // top-level re-export submodule, filled out after folding is finished toplevel_reexport: Option<Ident>, @@ -87,9 +79,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 } @@ -107,13 +103,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 } @@ -124,41 +120,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 { @@ -190,6 +163,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, @@ -242,6 +217,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<Ident>, @@ -279,12 +258,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<Symbol>, krate: ast::Crate, sd: &errors::Handler, - features: &Features) -> ast::Crate { + features: &Features, + test_runner: Option<ast::Path>) -> ast::Crate { // Remove the entry points let mut cleaner = EntryPointCleaner { depth: 0 }; let krate = cleaner.fold_crate(krate); @@ -298,19 +279,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, @@ -344,216 +326,64 @@ 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<ast::Item> { - 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<ast::Item> { // 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]); + + // If no test runner is provided we need to import the test crate + let main_body = if cx.test_runner.is_none() { + ecx.block(sp, vec![test_extern_stmt, call_test_main]) + } else { + ecx.block(sp, vec![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, @@ -561,71 +391,7 @@ fn mk_main(cx: &mut TestCtxt) -> P<ast::Item> { span: sp, tokens: None, }) -} - -fn mk_test_module(cx: &mut TestCtxt) -> (P<ast::Item>, Option<P<ast::Item>>) { - // 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 <ident>;` - 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: T) -> source_map::Spanned<T> { - source_map::Spanned { node: t, span: DUMMY_SP } -} - -fn path_node(ids: Vec<Ident>) -> 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 { @@ -640,184 +406,46 @@ fn path_name_i(idents: &[Ident]) -> String { path_name } -fn mk_tests(cx: &TestCtxt) -> P<ast::Item> { - // 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) +/// Creates a slice containing every test like so: +/// &[path::to::test1, path::to::test2] +fn mk_tests_slice(cx: &TestCtxt) -> P<ast::Expr> { + 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_addr_of(test.span, + ecx.expr_path(ecx.path(test.span, visible_path(cx, &test.path)))) + }).collect()) } -fn mk_test_descs(cx: &TestCtxt) -> P<ast::Expr> { - 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(), - }) -} - -fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> { - // 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<Ident>{ 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"); - } - }; - 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![], - ) - ], - ), - ) + cx.span_diagnostic.bug("expected to find top-level re-export name, but found None"); } - }; - - 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, "rustc_test_marker") +} - // 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<ast::Path> { + let 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() + } } |
