Skip to content

Commit 3f13b27

Browse files
committed
Auto merge of #53410 - djrenren:custom-test-frameworks, r=alexcrichton
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 0be2c30 + 0593dc7 commit 3f13b27

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+980
-627
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",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# `custom_test_frameworks`
2+
3+
The tracking issue for this feature is: [#50297]
4+
5+
[#50297]: https://github.com/rust-lang/rust/issues/50297
6+
7+
------------------------
8+
9+
The `custom_test_frameworks` feature allows the use of `#[test_case]` and `#![test_runner]`.
10+
Any function, const, or static can be annotated with `#[test_case]` causing it to be aggregated (like `#[test]`)
11+
and be passed to the test runner determined by the `#![test_runner]` crate attribute.
12+
13+
```rust
14+
#![feature(custom_test_frameworks)]
15+
#![test_runner(my_runner)]
16+
17+
fn my_runner(tests: &[&i32]) {
18+
for t in tests {
19+
if **t == 0 {
20+
println!("PASSED");
21+
} else {
22+
println!("FAILED");
23+
}
24+
}
25+
}
26+
27+
#[test_case]
28+
const WILL_PASS: i32 = 0;
29+
30+
#[test_case]
31+
const WILL_FAIL: i32 = 4;
32+
```
33+

src/librustc_driver/driver.rs

-1
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,6 @@ where
828828
let (mut krate, features) = syntax::config::features(
829829
krate,
830830
&sess.parse_sess,
831-
sess.opts.test,
832831
sess.edition(),
833832
);
834833
// these need to be set "early" so that expansion sees `quote` if enabled.

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, "rustc_test_marker") {
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/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,7 @@ pub struct Resolver<'a, 'b: 'a> {
14121412
crate_loader: &'a mut CrateLoader<'b>,
14131413
macro_names: FxHashSet<Ident>,
14141414
macro_prelude: FxHashMap<Name, &'a NameBinding<'a>>,
1415+
unshadowable_attrs: FxHashMap<Name, &'a NameBinding<'a>>,
14151416
pub all_macros: FxHashMap<Name, Def>,
14161417
macro_map: FxHashMap<DefId, Lrc<SyntaxExtension>>,
14171418
macro_defs: FxHashMap<Mark, DefId>,
@@ -1729,6 +1730,7 @@ impl<'a, 'crateloader: 'a> Resolver<'a, 'crateloader> {
17291730
crate_loader,
17301731
macro_names: FxHashSet(),
17311732
macro_prelude: FxHashMap(),
1733+
unshadowable_attrs: FxHashMap(),
17321734
all_macros: FxHashMap(),
17331735
macro_map: FxHashMap(),
17341736
invocations,

src/librustc_resolve/macros.rs

+23
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,23 @@ impl<'a, 'crateloader: 'a> base::Resolver for Resolver<'a, 'crateloader> {
207207
self.macro_prelude.insert(ident.name, binding);
208208
}
209209

210+
fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>) {
211+
let def_id = DefId {
212+
krate: BUILTIN_MACROS_CRATE,
213+
index: DefIndex::from_array_index(self.macro_map.len(),
214+
DefIndexAddressSpace::Low),
215+
};
216+
let kind = ext.kind();
217+
self.macro_map.insert(def_id, ext);
218+
let binding = self.arenas.alloc_name_binding(NameBinding {
219+
kind: NameBindingKind::Def(Def::Macro(def_id, kind), false),
220+
span: DUMMY_SP,
221+
vis: ty::Visibility::Invisible,
222+
expansion: Mark::root(),
223+
});
224+
self.unshadowable_attrs.insert(ident.name, binding);
225+
}
226+
210227
fn resolve_imports(&mut self) {
211228
ImportResolver { resolver: self }.resolve_imports()
212229
}
@@ -462,6 +479,12 @@ impl<'a, 'cl> Resolver<'a, 'cl> {
462479
return def;
463480
}
464481

482+
if kind == MacroKind::Attr {
483+
if let Some(ext) = self.unshadowable_attrs.get(&path[0].name) {
484+
return Ok(ext.def());
485+
}
486+
}
487+
465488
let legacy_resolution = self.resolve_legacy_scope(&invocation.legacy_scope, path[0], false);
466489
let result = if let Some((legacy_binding, _)) = legacy_resolution {
467490
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

+2-13
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,16 @@ use ptr::P;
2121

2222
/// A folder that strips out items that do not belong in the current configuration.
2323
pub struct StripUnconfigured<'a> {
24-
pub should_test: bool,
2524
pub sess: &'a ParseSess,
2625
pub features: Option<&'a Features>,
2726
}
2827

2928
// `cfg_attr`-process the crate's attributes and compute the crate's features.
30-
pub fn features(mut krate: ast::Crate, sess: &ParseSess, should_test: bool, edition: Edition)
29+
pub fn features(mut krate: ast::Crate, sess: &ParseSess, edition: Edition)
3130
-> (ast::Crate, Features) {
3231
let features;
3332
{
3433
let mut strip_unconfigured = StripUnconfigured {
35-
should_test,
3634
sess,
3735
features: None,
3836
};
@@ -118,11 +116,6 @@ impl<'a> StripUnconfigured<'a> {
118116
// Determine if a node with the given attributes should be included in this configuration.
119117
pub fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
120118
attrs.iter().all(|attr| {
121-
// When not compiling with --test we should not compile the #[test] functions
122-
if !self.should_test && is_test_or_bench(attr) {
123-
return false;
124-
}
125-
126119
let mis = if !is_cfg(attr) {
127120
return true;
128121
} else if let Some(mis) = attr.meta_item_list() {
@@ -249,7 +242,7 @@ impl<'a> StripUnconfigured<'a> {
249242
//
250243
// NB: This is intentionally not part of the fold_expr() function
251244
// 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)) {
245+
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
253246
let msg = "removing an expression is not supported in this position";
254247
self.sess.span_diagnostic.span_err(attr.span, msg);
255248
}
@@ -352,7 +345,3 @@ impl<'a> fold::Folder for StripUnconfigured<'a> {
352345
fn is_cfg(attr: &ast::Attribute) -> bool {
353346
attr.check_name("cfg")
354347
}
355-
356-
pub fn is_test_or_bench(attr: &ast::Attribute) -> bool {
357-
attr.check_name("test") || attr.check_name("bench")
358-
}

src/libsyntax/ext/base.rs

+3
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,7 @@ pub trait Resolver {
721721
fn visit_ast_fragment_with_placeholders(&mut self, mark: Mark, fragment: &AstFragment,
722722
derives: &[Mark]);
723723
fn add_builtin(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>);
724+
fn add_unshadowable_attr(&mut self, ident: ast::Ident, ext: Lrc<SyntaxExtension>);
724725

725726
fn resolve_imports(&mut self);
726727
// Resolves attribute and derive legacy macros from `#![plugin(..)]`.
@@ -729,6 +730,7 @@ pub trait Resolver {
729730

730731
fn resolve_macro_invocation(&mut self, invoc: &Invocation, scope: Mark, force: bool)
731732
-> Result<Option<Lrc<SyntaxExtension>>, Determinacy>;
733+
732734
fn resolve_macro_path(&mut self, path: &ast::Path, kind: MacroKind, scope: Mark,
733735
derives_in_scope: &[ast::Path], force: bool)
734736
-> Result<Lrc<SyntaxExtension>, Determinacy>;
@@ -759,6 +761,7 @@ impl Resolver for DummyResolver {
759761
fn visit_ast_fragment_with_placeholders(&mut self, _invoc: Mark, _fragment: &AstFragment,
760762
_derives: &[Mark]) {}
761763
fn add_builtin(&mut self, _ident: ast::Ident, _ext: Lrc<SyntaxExtension>) {}
764+
fn add_unshadowable_attr(&mut self, _ident: ast::Ident, _ext: Lrc<SyntaxExtension>) {}
762765

763766
fn resolve_imports(&mut self) {}
764767
fn find_legacy_attr_invoc(&mut self, _attrs: &mut Vec<Attribute>, _allow_derive: bool)

0 commit comments

Comments
 (0)