Skip to content

Commit

Permalink
Auto merge of #53410 - djrenren:custom-test-frameworks, r=alexcrichton
Browse files Browse the repository at this point in the history
Introduce Custom Test Frameworks

Introduces `#[test_case]` and `#[test_runner]` and re-implements `#[test]` and `#[bench]` in terms of them.

Details found here: https://blog.jrenner.net/rust/testing/2018/08/06/custom-test-framework-prop.html
  • Loading branch information
bors committed Sep 5, 2018
2 parents 0be2c30 + 0593dc7 commit 3f13b27
Show file tree
Hide file tree
Showing 42 changed files with 980 additions and 627 deletions.
1 change: 1 addition & 0 deletions src/Cargo.lock
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# `custom_test_frameworks`

The tracking issue for this feature is: [#50297]

[#50297]: https://github.com/rust-lang/rust/issues/50297

------------------------

The `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`.
Any function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`)
and be passed to the test runner determined by the `#![test_runner]` crate attribute.

```rust
#![feature(custom_test_frameworks)]
#![test_runner(my_runner)]

fn my_runner(tests: &[&i32]) {
for t in tests {
if **t == 0 {
println!("PASSED");
} else {
println!("FAILED");
}
}
}

#[test_case]
const WILL_PASS: i32 = 0;

#[test_case]
const WILL_FAIL: i32 = 4;
```

1 change: 0 additions & 1 deletion src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,6 @@ where
let (mut krate, features) = syntax::config::features(
krate,
&sess.parse_sess,
sess.opts.test,
sess.edition(),
);
// these need to be set "early" so that expansion sees `quote` if enabled.
Expand Down
67 changes: 40 additions & 27 deletions src/librustc_lint/builtin.rs
Original file line number Diff line number Diff line change
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, "rustc_test_marker") {
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
Original file line number Diff line number Diff line change
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
2 changes: 2 additions & 0 deletions src/librustc_resolve/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,6 +1412,7 @@ pub struct Resolver<'a, 'b: 'a> {
crate_loader: &'a mut CrateLoader<'b>,
macro_names: FxHashSet<Ident>,
macro_prelude: FxHashMap<Name, &'a NameBinding<'a>>,
unshadowable_attrs: FxHashMap<Name, &'a NameBinding<'a>>,
pub all_macros: FxHashMap<Name, Def>,
macro_map: FxHashMap<DefId, Lrc<SyntaxExtension>>,
macro_defs: FxHashMap<Mark, DefId>,
Expand Down Expand Up @@ -1729,6 +1730,7 @@ impl<'a, 'crateloader: 'a> Resolver<'a, 'crateloader> {
crate_loader,
macro_names: FxHashSet(),
macro_prelude: FxHashMap(),
unshadowable_attrs: FxHashMap(),
all_macros: FxHashMap(),
macro_map: FxHashMap(),
invocations,
Expand Down
23 changes: 23 additions & 0 deletions src/librustc_resolve/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ impl<'a, 'crateloader: 'a> base::Resolver for Resolver<'a, 'crateloader> {
self.macro_prelude.insert(ident.name, binding);
}

fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>) {
let def_id = DefId {
krate: BUILTIN_MACROS_CRATE,
index: DefIndex::from_array_index(self.macro_map.len(),
DefIndexAddressSpace::Low),
};
let kind = ext.kind();
self.macro_map.insert(def_id, ext);
let binding = self.arenas.alloc_name_binding(NameBinding {
kind: NameBindingKind::Def(Def::Macro(def_id, kind), false),
span: DUMMY_SP,
vis: ty::Visibility::Invisible,
expansion: Mark::root(),
});
self.unshadowable_attrs.insert(ident.name, binding);
}

fn resolve_imports(&mut self) {
ImportResolver { resolver: self }.resolve_imports()
}
Expand Down Expand Up @@ -462,6 +479,12 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
return def;
}

if kind == MacroKind::Attr {
if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) {
return Ok(ext.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
Original file line number Diff line number Diff line change
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
15 changes: 2 additions & 13 deletions src/libsyntax/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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")
}
3 changes: 3 additions & 0 deletions src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..)]`.
Expand All @@ -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>;
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 3f13b27

Please sign in to comment.