Skip to content

Commit 9b27de4

Browse files
committed
Introduce Custom Test Frameworks
1 parent 0be2c30 commit 9b27de4

35 files changed

+804
-574
lines changed

src/Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2739,6 +2739,7 @@ name = "syntax_ext"
27392739
version = "0.0.0"
27402740
dependencies = [
27412741
"fmt_macros 0.0.0",
2742+
"log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
27422743
"proc_macro 0.0.0",
27432744
"rustc_data_structures 0.0.0",
27442745
"rustc_errors 0.0.0",

src/librustc_lint/builtin.rs

+40-27
Original file line numberDiff line numberDiff line change
@@ -1835,43 +1835,56 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
18351835
}
18361836

18371837
declare_lint! {
1838-
UNNAMEABLE_TEST_FUNCTIONS,
1838+
UNNAMEABLE_TEST_ITEMS,
18391839
Warn,
1840-
"detects an function that cannot be named being marked as #[test]"
1840+
"detects an item that cannot be named being marked as #[test_case]",
1841+
report_in_external_macro: true
1842+
}
1843+
1844+
pub struct UnnameableTestItems {
1845+
boundary: ast::NodeId, // NodeId of the item under which things are not nameable
1846+
items_nameable: bool,
18411847
}
18421848

1843-
pub struct UnnameableTestFunctions;
1849+
impl UnnameableTestItems {
1850+
pub fn new() -> Self {
1851+
Self {
1852+
boundary: ast::DUMMY_NODE_ID,
1853+
items_nameable: true
1854+
}
1855+
}
1856+
}
18441857

1845-
impl LintPass for UnnameableTestFunctions {
1858+
impl LintPass for UnnameableTestItems {
18461859
fn get_lints(&self) -> LintArray {
1847-
lint_array!(UNNAMEABLE_TEST_FUNCTIONS)
1860+
lint_array!(UNNAMEABLE_TEST_ITEMS)
18481861
}
18491862
}
18501863

1851-
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestFunctions {
1864+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
18521865
fn check_item(&mut self, cx: &LateContext, it: &hir::Item) {
1853-
match it.node {
1854-
hir::ItemKind::Fn(..) => {
1855-
for attr in &it.attrs {
1856-
if attr.name() == "test" {
1857-
let parent = cx.tcx.hir.get_parent(it.id);
1858-
match cx.tcx.hir.find(parent) {
1859-
Some(Node::Item(hir::Item {node: hir::ItemKind::Mod(_), ..})) |
1860-
None => {}
1861-
_ => {
1862-
cx.struct_span_lint(
1863-
UNNAMEABLE_TEST_FUNCTIONS,
1864-
attr.span,
1865-
"cannot test inner function",
1866-
).emit();
1867-
}
1868-
}
1869-
break;
1870-
}
1871-
}
1866+
if self.items_nameable {
1867+
if let hir::ItemKind::Mod(..) = it.node {}
1868+
else {
1869+
self.items_nameable = false;
1870+
self.boundary = it.id;
18721871
}
1873-
_ => return,
1874-
};
1872+
return;
1873+
}
1874+
1875+
if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") {
1876+
cx.struct_span_lint(
1877+
UNNAMEABLE_TEST_ITEMS,
1878+
attr.span,
1879+
"cannot test inner items",
1880+
).emit();
1881+
}
1882+
}
1883+
1884+
fn check_item_post(&mut self, _cx: &LateContext, it: &hir::Item) {
1885+
if !self.items_nameable && self.boundary == it.id {
1886+
self.items_nameable = true;
1887+
}
18751888
}
18761889
}
18771890

src/librustc_lint/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
149149
MutableTransmutes: MutableTransmutes,
150150
UnionsWithDropFields: UnionsWithDropFields,
151151
UnreachablePub: UnreachablePub,
152-
UnnameableTestFunctions: UnnameableTestFunctions,
152+
UnnameableTestItems: UnnameableTestItems::new(),
153153
TypeAliasBounds: TypeAliasBounds,
154154
UnusedBrokenConst: UnusedBrokenConst,
155155
TrivialConstraints: TrivialConstraints,

src/librustc_resolve/macros.rs

+4
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,10 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
462462
return def;
463463
}
464464

465+
if kind == MacroKind::Attr && *&path[0].as_str() == "test" {
466+
return Ok(self.macro_prelude.get(&path[0].name).unwrap().def())
467+
}
468+
465469
let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false);
466470
let result = if let Some((legacy_binding, _)) = legacy_resolution {
467471
Ok(legacy_binding.def())

src/libsyntax/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1587,7 +1587,7 @@ impl TyKind {
15871587
if let TyKind::ImplicitSelf = *self { true } else { false }
15881588
}
15891589

1590-
crate fn is_unit(&self) -> bool {
1590+
pub fn is_unit(&self) -> bool {
15911591
if let TyKind::Tup(ref tys) = *self { tys.is_empty() } else { false }
15921592
}
15931593
}

src/libsyntax/config.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl<'a> StripUnconfigured<'a> {
119119
pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
120120
attrs.iter().all(|attr| {
121121
// When not compiling with --test we should not compile the #[test] functions
122-
if !self.should_test && is_test_or_bench(attr) {
122+
if !self.should_test && is_test(attr) {
123123
return false;
124124
}
125125

@@ -249,7 +249,7 @@ impl<'a> StripUnconfigured<'a> {
249249
//
250250
// NB: This is intentionally not part of the fold_expr() function
251251
// in order for fold_opt_expr() to be able to avoid this check
252-
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test_or_bench(a)) {
252+
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a) || is_test(a)) {
253253
let msg = "removing an expression is not supported in this position";
254254
self.sess.span_diagnostic.span_err(attr.span, msg);
255255
}
@@ -353,6 +353,6 @@ fn is_cfg(attr: &ast::Attribute) -> bool {
353353
attr.check_name("cfg")
354354
}
355355

356-
pub fn is_test_or_bench(attr: &ast::Attribute) -> bool {
357-
attr.check_name("test") || attr.check_name("bench")
356+
pub fn is_test(att: &ast::Attribute) -> bool {
357+
att.check_name("test_case")
358358
}

src/libsyntax/ext/expand.rs

+10-38
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ use ast::{self, Block, Ident, NodeId, PatKind, Path};
1212
use ast::{MacStmtStyle, StmtKind, ItemKind};
1313
use attr::{self, HasAttrs};
1414
use source_map::{ExpnInfo, MacroBang, MacroAttribute, dummy_spanned, respan};
15-
use config::{is_test_or_bench, StripUnconfigured};
15+
use config::StripUnconfigured;
1616
use errors::{Applicability, FatalError};
1717
use ext::base::*;
18-
use ext::build::AstBuilder;
1918
use ext::derive::{add_derived_markers, collect_derives};
2019
use ext::hygiene::{self, Mark, SyntaxContext};
2120
use ext::placeholders::{placeholder, PlaceholderExpander};
@@ -37,7 +36,6 @@ use visit::{self, Visitor};
3736
use rustc_data_structures::fx::FxHashMap;
3837
use std::fs::File;
3938
use std::io::Read;
40-
use std::iter::FromIterator;
4139
use std::{iter, mem};
4240
use std::rc::Rc;
4341
use std::path::PathBuf;
@@ -1366,51 +1364,25 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13661364
self.cx.current_expansion.directory_ownership = orig_directory_ownership;
13671365
result
13681366
}
1369-
// Ensure that test functions are accessible from the test harness.
1367+
1368+
// Ensure that test items can be exported by the harness generator.
13701369
// #[test] fn foo() {}
13711370
// becomes:
13721371
// #[test] pub fn foo_gensym(){}
1373-
// #[allow(unused)]
1374-
// use foo_gensym as foo;
1375-
ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1376-
if self.tests_nameable && item.attrs.iter().any(|attr| is_test_or_bench(attr)) {
1377-
let orig_ident = item.ident;
1378-
let orig_vis = item.vis.clone();
1379-
1372+
ast::ItemKind::Const(..)
1373+
| ast::ItemKind::Static(..)
1374+
| ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1375+
if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") {
13801376
// Publicize the item under gensymed name to avoid pollution
1377+
// This means #[test_case] items can't be referenced by user code
13811378
item = item.map(|mut item| {
13821379
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
13831380
item.ident = item.ident.gensym();
13841381
item
13851382
});
1386-
1387-
// Use the gensymed name under the item's original visibility
1388-
let mut use_item = self.cx.item_use_simple_(
1389-
item.ident.span,
1390-
orig_vis,
1391-
Some(orig_ident),
1392-
self.cx.path(item.ident.span,
1393-
vec![keywords::SelfValue.ident(), item.ident]));
1394-
1395-
// #[allow(unused)] because the test function probably isn't being referenced
1396-
use_item = use_item.map(|mut ui| {
1397-
ui.attrs.push(
1398-
self.cx.attribute(DUMMY_SP, attr::mk_list_item(DUMMY_SP,
1399-
Ident::from_str("allow"), vec![
1400-
attr::mk_nested_word_item(Ident::from_str("unused"))
1401-
]
1402-
))
1403-
);
1404-
1405-
ui
1406-
});
1407-
1408-
OneVector::from_iter(
1409-
self.fold_unnameable(item).into_iter()
1410-
.chain(self.fold_unnameable(use_item)))
1411-
} else {
1412-
self.fold_unnameable(item)
14131383
}
1384+
1385+
self.fold_unnameable(item)
14141386
}
14151387
_ => self.fold_unnameable(item),
14161388
}

src/libsyntax/feature_gate.rs

+12
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,10 @@ declare_features! (
515515

516516
// unsized rvalues at arguments and parameters
517517
(active, unsized_locals, "1.30.0", Some(48055), None),
518+
519+
// #![test_runner]
520+
// #[test_case]
521+
(active, custom_test_frameworks, "1.30.0", Some(50297), None),
518522
);
519523

520524
declare_features! (
@@ -775,6 +779,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
775779
("no_link", Normal, Ungated),
776780
("derive", Normal, Ungated),
777781
("should_panic", Normal, Ungated),
782+
("test_case", Normal, Gated(Stability::Unstable,
783+
"custom_test_frameworks",
784+
"Custom test frameworks are experimental",
785+
cfg_fn!(custom_test_frameworks))),
778786
("ignore", Normal, Ungated),
779787
("no_implicit_prelude", Normal, Ungated),
780788
("reexport_test_harness_main", Normal, Ungated),
@@ -1156,6 +1164,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
11561164
("no_builtins", CrateLevel, Ungated),
11571165
("recursion_limit", CrateLevel, Ungated),
11581166
("type_length_limit", CrateLevel, Ungated),
1167+
("test_runner", CrateLevel, Gated(Stability::Unstable,
1168+
"custom_test_frameworks",
1169+
"Custom Test Frameworks is an unstable feature",
1170+
cfg_fn!(custom_test_frameworks))),
11591171
];
11601172

11611173
// cfg(...)'s that are feature gated

0 commit comments

Comments
 (0)