Skip to content

Commit

Permalink
Introduce Custom Test Frameworks
Browse files Browse the repository at this point in the history
  • Loading branch information
djrenren committed Sep 5, 2018
1 parent 0be2c30 commit 9b27de4
Show file tree
Hide file tree
Showing 35 changed files with 804 additions and 574 deletions.
1 change: 1 addition & 0 deletions src/Cargo.lock
Expand Up @@ -2739,6 +2739,7 @@ name = "syntax_ext"
version = "0.0.0"
dependencies = [
"fmt_macros 0.0.0",
"log 0.4.4 (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",
Expand Down
67 changes: 40 additions & 27 deletions src/librustc_lint/builtin.rs
Expand Up @@ -1835,43 +1835,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 UnnameableTestItems {
boundary: ast::NodeId, // NodeId of the item under which things are not nameable
items_nameable: bool,
}

pub struct UnnameableTestFunctions;
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(Node::Item(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;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_lint/lib.rs
Expand Up @@ -149,7 +149,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,
Expand Down
4 changes: 4 additions & 0 deletions src/librustc_resolve/macros.rs
Expand Up @@ -462,6 +462,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())
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/ast.rs
Expand Up @@ -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 }
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/libsyntax/config.rs
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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")
}
48 changes: 10 additions & 38 deletions src/libsyntax/ext/expand.rs
Expand Up @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -1366,51 +1364,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::from_iter(
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),
}
Expand Down
12 changes: 12 additions & 0 deletions src/libsyntax/feature_gate.rs
Expand Up @@ -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! (
Expand Down Expand Up @@ -775,6 +779,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),
Expand Down Expand Up @@ -1156,6 +1164,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
Expand Down

0 comments on commit 9b27de4

Please sign in to comment.