Skip to content

Commit 424c902

Browse files
committed
Auto merge of #53410 - djrenren:custom-test-frameworks, r=<try>
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
2 parents b75b047 + 44db252 commit 424c902

30 files changed

+596
-584
lines changed

src/Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2770,6 +2770,7 @@ name = "syntax_ext"
27702770
version = "0.0.0"
27712771
dependencies = [
27722772
"fmt_macros 0.0.0",
2773+
"log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
27732774
"proc_macro 0.0.0",
27742775
"rustc_data_structures 0.0.0",
27752776
"rustc_errors 0.0.0",

src/librustc_lint/builtin.rs

+40-27
Original file line numberDiff line numberDiff line change
@@ -1842,43 +1842,56 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
18421842
}
18431843

18441844
declare_lint! {
1845-
UNNAMEABLE_TEST_FUNCTIONS,
1845+
UNNAMEABLE_TEST_ITEMS,
18461846
Warn,
1847-
"detects an function that cannot be named being marked as #[test]"
1847+
"detects an item that cannot be named being marked as #[test_case]",
1848+
report_in_external_macro: true
18481849
}
18491850

1850-
pub struct UnnameableTestFunctions;
1851+
pub struct UnnameableTestItems {
1852+
boundary: ast::NodeId, // NodeId of the item under which things are not nameable
1853+
items_nameable: bool,
1854+
}
1855+
1856+
impl UnnameableTestItems {
1857+
pub fn new() -> Self {
1858+
Self {
1859+
boundary: ast::DUMMY_NODE_ID,
1860+
items_nameable: true
1861+
}
1862+
}
1863+
}
18511864

1852-
impl LintPass for UnnameableTestFunctions {
1865+
impl LintPass for UnnameableTestItems {
18531866
fn get_lints(&self) -> LintArray {
1854-
lint_array!(UNNAMEABLE_TEST_FUNCTIONS)
1867+
lint_array!(UNNAMEABLE_TEST_ITEMS)
18551868
}
18561869
}
18571870

1858-
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestFunctions {
1871+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnnameableTestItems {
18591872
fn check_item(&mut self, cx: &LateContext, it: &hir::Item) {
1860-
match it.node {
1861-
hir::ItemKind::Fn(..) => {
1862-
for attr in &it.attrs {
1863-
if attr.name() == "test" {
1864-
let parent = cx.tcx.hir.get_parent(it.id);
1865-
match cx.tcx.hir.find(parent) {
1866-
Some(hir_map::NodeItem(hir::Item {node: hir::ItemKind::Mod(_), ..})) |
1867-
None => {}
1868-
_ => {
1869-
cx.struct_span_lint(
1870-
UNNAMEABLE_TEST_FUNCTIONS,
1871-
attr.span,
1872-
"cannot test inner function",
1873-
).emit();
1874-
}
1875-
}
1876-
break;
1877-
}
1878-
}
1873+
if self.items_nameable {
1874+
if let hir::ItemKind::Mod(..) = it.node {}
1875+
else {
1876+
self.items_nameable = false;
1877+
self.boundary = it.id;
18791878
}
1880-
_ => return,
1881-
};
1879+
return;
1880+
}
1881+
1882+
if let Some(attr) = attr::find_by_name(&it.attrs, "test_case") {
1883+
cx.struct_span_lint(
1884+
UNNAMEABLE_TEST_ITEMS,
1885+
attr.span,
1886+
"cannot test inner items",
1887+
).emit();
1888+
}
1889+
}
1890+
1891+
fn check_item_post(&mut self, _cx: &LateContext, it: &hir::Item) {
1892+
if !self.items_nameable && self.boundary == it.id {
1893+
self.items_nameable = true;
1894+
}
18821895
}
18831896
}
18841897

src/librustc_lint/lib.rs

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

src/librustc_resolve/macros.rs

+4
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,10 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
475475
return def;
476476
}
477477

478+
if kind == MacroKind::Attr && *&path[0].as_str() == "test" {
479+
return Ok(self.macro_prelude.get(&path[0].name).unwrap().def())
480+
}
481+
478482
let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false);
479483
let result = if let Some((legacy_binding, _)) = legacy_resolution {
480484
Ok(legacy_binding.def())

src/libsyntax/ast.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1582,7 +1582,7 @@ impl TyKind {
15821582
if let TyKind::ImplicitSelf = *self { true } else { false }
15831583
}
15841584

1585-
crate fn is_unit(&self) -> bool {
1585+
pub fn is_unit(&self) -> bool {
15861586
if let TyKind::Tup(ref tys) = *self { tys.is_empty() } else { false }
15871587
}
15881588
}

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-37
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};
@@ -1370,51 +1369,25 @@ impl<'a, 'b> Folder for InvocationCollector<'a, 'b> {
13701369
self.cx.current_expansion.directory_ownership = orig_directory_ownership;
13711370
result
13721371
}
1373-
// Ensure that test functions are accessible from the test harness.
1372+
1373+
// Ensure that test items can be exported by the harness generator.
13741374
// #[test] fn foo() {}
13751375
// becomes:
13761376
// #[test] pub fn foo_gensym(){}
1377-
// #[allow(unused)]
1378-
// use foo_gensym as foo;
1379-
ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1380-
if self.tests_nameable && item.attrs.iter().any(|attr| is_test_or_bench(attr)) {
1381-
let orig_ident = item.ident;
1382-
let orig_vis = item.vis.clone();
1383-
1377+
ast::ItemKind::Const(..)
1378+
| ast::ItemKind::Static(..)
1379+
| ast::ItemKind::Fn(..) if self.cx.ecfg.should_test => {
1380+
if self.tests_nameable && attr::contains_name(&item.attrs, "test_case") {
13841381
// Publicize the item under gensymed name to avoid pollution
1382+
// This means #[test_case] items can't be referenced by user code
13851383
item = item.map(|mut item| {
13861384
item.vis = respan(item.vis.span, ast::VisibilityKind::Public);
13871385
item.ident = item.ident.gensym();
13881386
item
13891387
});
1390-
1391-
// Use the gensymed name under the item's original visibility
1392-
let mut use_item = self.cx.item_use_simple_(
1393-
item.ident.span,
1394-
orig_vis,
1395-
Some(orig_ident),
1396-
self.cx.path(item.ident.span,
1397-
vec![keywords::SelfValue.ident(), item.ident]));
1398-
1399-
// #[allow(unused)] because the test function probably isn't being referenced
1400-
use_item = use_item.map(|mut ui| {
1401-
ui.attrs.push(
1402-
self.cx.attribute(DUMMY_SP, attr::mk_list_item(DUMMY_SP,
1403-
Ident::from_str("allow"), vec![
1404-
attr::mk_nested_word_item(Ident::from_str("unused"))
1405-
]
1406-
))
1407-
);
1408-
1409-
ui
1410-
});
1411-
1412-
OneVector::many(
1413-
self.fold_unnameable(item).into_iter()
1414-
.chain(self.fold_unnameable(use_item)))
1415-
} else {
1416-
self.fold_unnameable(item)
14171388
}
1389+
1390+
self.fold_unnameable(item)
14181391
}
14191392
_ => self.fold_unnameable(item),
14201393
}

src/libsyntax/feature_gate.rs

+12
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ declare_features! (
503503

504504
// unsized rvalues at arguments and parameters
505505
(active, unsized_locals, "1.30.0", Some(48055), None),
506+
507+
// #![test_runner]
508+
// #[test_case]
509+
(active, custom_test_frameworks, "1.30.0", Some(50297), None),
506510
);
507511

508512
declare_features! (
@@ -757,6 +761,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
757761
("no_link", Normal, Ungated),
758762
("derive", Normal, Ungated),
759763
("should_panic", Normal, Ungated),
764+
("test_case", Normal, Gated(Stability::Unstable,
765+
"custom_test_frameworks",
766+
"Custom test frameworks are experimental",
767+
cfg_fn!(custom_test_frameworks))),
760768
("ignore", Normal, Ungated),
761769
("no_implicit_prelude", Normal, Ungated),
762770
("reexport_test_harness_main", Normal, Ungated),
@@ -1123,6 +1131,10 @@ pub const BUILTIN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeG
11231131
("no_builtins", CrateLevel, Ungated),
11241132
("recursion_limit", CrateLevel, Ungated),
11251133
("type_length_limit", CrateLevel, Ungated),
1134+
("test_runner", CrateLevel, Gated(Stability::Unstable,
1135+
"custom_test_frameworks",
1136+
"Custom Test Frameworks is an unstable feature",
1137+
cfg_fn!(custom_test_frameworks))),
11261138
];
11271139

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

0 commit comments

Comments
 (0)