Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 424c902

Browse files
committedAug 22, 2018
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 40 additions & 27 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 10 additions & 37 deletions
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

Lines changed: 12 additions & 0 deletions
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

‎src/libsyntax/test.rs

Lines changed: 100 additions & 476 deletions
Large diffs are not rendered by default.

‎src/libsyntax_ext/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ rustc_errors = { path = "../librustc_errors" }
1515
syntax = { path = "../libsyntax" }
1616
syntax_pos = { path = "../libsyntax_pos" }
1717
rustc_data_structures = { path = "../librustc_data_structures" }
18-
rustc_target = { path = "../librustc_target" }
18+
rustc_target = { path = "../librustc_target" }
19+
log = "0.4"

‎src/libsyntax_ext/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#![feature(decl_macro)]
1919
#![cfg_attr(not(stage0), feature(nll))]
2020
#![feature(str_escape)]
21-
21+
#![feature(quote)]
2222
#![feature(rustc_diagnostic_macros)]
2323

2424
extern crate fmt_macros;
@@ -29,6 +29,7 @@ extern crate proc_macro;
2929
extern crate rustc_data_structures;
3030
extern crate rustc_errors as errors;
3131
extern crate rustc_target;
32+
#[macro_use] extern crate log;
3233

3334
mod diagnostics;
3435

@@ -48,6 +49,7 @@ mod format_foreign;
4849
mod global_asm;
4950
mod log_syntax;
5051
mod trace_macros;
52+
mod test;
5153

5254
pub mod proc_macro_registrar;
5355

@@ -56,7 +58,7 @@ pub mod proc_macro_impl;
5658

5759
use rustc_data_structures::sync::Lrc;
5860
use syntax::ast;
59-
use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension};
61+
use syntax::ext::base::{MacroExpanderFn, NormalTT, NamedSyntaxExtension, MultiModifier};
6062
use syntax::ext::hygiene;
6163
use syntax::symbol::Symbol;
6264

@@ -127,6 +129,9 @@ pub fn register_builtins(resolver: &mut dyn syntax::ext::base::Resolver,
127129
assert: assert::expand_assert,
128130
}
129131

132+
register(Symbol::intern("test"), MultiModifier(Box::new(test::expand_test)));
133+
register(Symbol::intern("bench"), MultiModifier(Box::new(test::expand_bench)));
134+
130135
// format_args uses `unstable` things internally.
131136
register(Symbol::intern("format_args"),
132137
NormalTT {

‎src/libsyntax_ext/test.rs

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
/// The expansion from a test function to the appropriate test struct for libtest
12+
/// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
13+
14+
use syntax::ext::base::*;
15+
use syntax::ext::build::AstBuilder;
16+
use syntax::ext::hygiene::{self, Mark, SyntaxContext};
17+
use syntax::attr;
18+
use syntax::ast;
19+
use syntax::print::pprust;
20+
use syntax::symbol::Symbol;
21+
use syntax_pos::{DUMMY_SP, Span};
22+
use syntax::source_map::{ExpnInfo, MacroAttribute};
23+
use std::iter;
24+
25+
pub fn expand_test(
26+
cx: &mut ExtCtxt,
27+
attr_sp: Span,
28+
_meta_item: &ast::MetaItem,
29+
item: Annotatable,
30+
) -> Vec<Annotatable> {
31+
expand_test_or_bench(cx, attr_sp, item, false)
32+
}
33+
34+
pub fn expand_bench(
35+
cx: &mut ExtCtxt,
36+
attr_sp: Span,
37+
_meta_item: &ast::MetaItem,
38+
item: Annotatable,
39+
) -> Vec<Annotatable> {
40+
expand_test_or_bench(cx, attr_sp, item, true)
41+
}
42+
43+
pub fn expand_test_or_bench(
44+
cx: &mut ExtCtxt,
45+
attr_sp: Span,
46+
item: Annotatable,
47+
is_bench: bool
48+
) -> Vec<Annotatable> {
49+
// If we're not in test configuration, remove the annotated item
50+
if !cx.ecfg.should_test { return vec![]; }
51+
52+
let item =
53+
if let Annotatable::Item(i) = item { i }
54+
else {
55+
cx.parse_sess.span_diagnostic.span_fatal(item.span(),
56+
"#[test] attribute is only allowed on fn items").raise();
57+
};
58+
59+
// has_*_signature will report any errors in the type so compilation
60+
// will fail. We shouldn't try to expand in this case because the errors
61+
// would be spurious.
62+
if (!is_bench && !has_test_signature(cx, &item)) ||
63+
(is_bench && !has_bench_signature(cx, &item)) {
64+
return vec![Annotatable::Item(item)];
65+
}
66+
67+
let (sp, attr_sp) = {
68+
let mark = Mark::fresh(Mark::root());
69+
mark.set_expn_info(ExpnInfo {
70+
call_site: DUMMY_SP,
71+
def_site: None,
72+
format: MacroAttribute(Symbol::intern("test")),
73+
allow_internal_unstable: true,
74+
allow_internal_unsafe: false,
75+
local_inner_macros: false,
76+
edition: hygiene::default_edition(),
77+
});
78+
(item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
79+
attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
80+
};
81+
82+
// Gensym "test" so we can extern crate without conflicting with any local names
83+
let test_id = cx.ident_of("test").gensym();
84+
85+
// creates test::$name
86+
let test_path = |name| {
87+
cx.path(sp, vec![test_id, cx.ident_of(name)])
88+
};
89+
90+
// creates test::$name
91+
let should_panic_path = |name| {
92+
cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)])
93+
};
94+
95+
// creates $name: $expr
96+
let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
97+
98+
let test_fn = if is_bench {
99+
// A simple ident for a lambda
100+
let b = cx.ident_of("b");
101+
102+
cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
103+
// |b| self::test::assert_test_result(
104+
cx.lambda1(sp,
105+
cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
106+
// super::$test_fn(b)
107+
cx.expr_call(sp,
108+
cx.expr_path(cx.path(sp, vec![item.ident])),
109+
vec![cx.expr_ident(sp, b)])
110+
]),
111+
b
112+
)
113+
// )
114+
])
115+
} else {
116+
cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
117+
// || {
118+
cx.lambda0(sp,
119+
// test::assert_test_result(
120+
cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
121+
// $test_fn()
122+
cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
123+
// )
124+
])
125+
// }
126+
)
127+
// )
128+
])
129+
};
130+
131+
let mut test_const = cx.item(sp, item.ident.gensym(),
132+
// #[test_case]
133+
vec![cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("test_case")))],
134+
// const $ident: test::TestDescAndFn =
135+
ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
136+
// test::TestDescAndFn {
137+
cx.expr_struct(sp, test_path("TestDescAndFn"), vec![
138+
// desc: test::TestDesc {
139+
field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![
140+
// name: "path::to::test"
141+
field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")),
142+
vec![
143+
cx.expr_str(sp, Symbol::intern(&item_path(
144+
&cx.current_expansion.module.mod_path,
145+
&item.ident
146+
)))
147+
])),
148+
// ignore: true | false
149+
field("ignore", cx.expr_bool(sp, should_ignore(&item))),
150+
// allow_fail: true | false
151+
field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
152+
// should_panic: ...
153+
field("should_panic", match should_panic(cx, &item) {
154+
// test::ShouldPanic::No
155+
ShouldPanic::No => cx.expr_path(should_panic_path("No")),
156+
// test::ShouldPanic::Yes
157+
ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")),
158+
// test::ShouldPanic::YesWithMessage("...")
159+
ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp,
160+
cx.expr_path(should_panic_path("YesWithMessage")),
161+
vec![cx.expr_str(sp, sym)]),
162+
}),
163+
// },
164+
])),
165+
// testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
166+
field("testfn", test_fn)
167+
// }
168+
])
169+
// }
170+
));
171+
test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
172+
173+
// extern crate test as test_gensym
174+
let test_extern = cx.item(sp,
175+
test_id,
176+
vec![],
177+
ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
178+
);
179+
180+
debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
181+
182+
vec![
183+
// Access to libtest under a gensymed name
184+
Annotatable::Item(test_extern),
185+
// The generated test case
186+
Annotatable::Item(test_const),
187+
// The original item
188+
Annotatable::Item(item)
189+
]
190+
}
191+
192+
fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String {
193+
mod_path.iter().chain(iter::once(item_ident))
194+
.map(|x| x.to_string()).collect::<Vec<String>>().join("::")
195+
}
196+
197+
enum ShouldPanic {
198+
No,
199+
Yes(Option<Symbol>),
200+
}
201+
202+
fn should_ignore(i: &ast::Item) -> bool {
203+
attr::contains_name(&i.attrs, "ignore")
204+
}
205+
206+
fn should_fail(i: &ast::Item) -> bool {
207+
attr::contains_name(&i.attrs, "allow_fail")
208+
}
209+
210+
fn should_panic(cx: &ExtCtxt, i: &ast::Item) -> ShouldPanic {
211+
match attr::find_by_name(&i.attrs, "should_panic") {
212+
Some(attr) => {
213+
let ref sd = cx.parse_sess.span_diagnostic;
214+
if attr.is_value_str() {
215+
sd.struct_span_warn(
216+
attr.span(),
217+
"attribute must be of the form: \
218+
`#[should_panic]` or \
219+
`#[should_panic(expected = \"error message\")]`"
220+
).note("Errors in this attribute were erroneously allowed \
221+
and will become a hard error in a future release.")
222+
.emit();
223+
return ShouldPanic::Yes(None);
224+
}
225+
match attr.meta_item_list() {
226+
// Handle #[should_panic]
227+
None => ShouldPanic::Yes(None),
228+
// Handle #[should_panic(expected = "foo")]
229+
Some(list) => {
230+
let msg = list.iter()
231+
.find(|mi| mi.check_name("expected"))
232+
.and_then(|mi| mi.meta_item())
233+
.and_then(|mi| mi.value_str());
234+
if list.len() != 1 || msg.is_none() {
235+
sd.struct_span_warn(
236+
attr.span(),
237+
"argument must be of the form: \
238+
`expected = \"error message\"`"
239+
).note("Errors in this attribute were erroneously \
240+
allowed and will become a hard error in a \
241+
future release.").emit();
242+
ShouldPanic::Yes(None)
243+
} else {
244+
ShouldPanic::Yes(msg)
245+
}
246+
},
247+
}
248+
}
249+
None => ShouldPanic::No,
250+
}
251+
}
252+
253+
fn has_test_signature(cx: &ExtCtxt, i: &ast::Item) -> bool {
254+
let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic");
255+
let ref sd = cx.parse_sess.span_diagnostic;
256+
if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node {
257+
if header.unsafety == ast::Unsafety::Unsafe {
258+
sd.span_err(
259+
i.span,
260+
"unsafe functions cannot be used for tests"
261+
);
262+
return false
263+
}
264+
if header.asyncness.is_async() {
265+
sd.span_err(
266+
i.span,
267+
"async functions cannot be used for tests"
268+
);
269+
return false
270+
}
271+
272+
273+
// If the termination trait is active, the compiler will check that the output
274+
// type implements the `Termination` trait as `libtest` enforces that.
275+
let has_output = match decl.output {
276+
ast::FunctionRetTy::Default(..) => false,
277+
ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false,
278+
_ => true
279+
};
280+
281+
if !decl.inputs.is_empty() {
282+
sd.span_err(i.span, "functions used as tests can not have any arguments");
283+
return false;
284+
}
285+
286+
match (has_output, has_should_panic_attr) {
287+
(true, true) => {
288+
sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
289+
false
290+
},
291+
(true, false) => if !generics.params.is_empty() {
292+
sd.span_err(i.span,
293+
"functions used as tests must have signature fn() -> ()");
294+
false
295+
} else {
296+
true
297+
},
298+
(false, _) => true
299+
}
300+
} else {
301+
sd.span_err(i.span, "only functions may be used as tests");
302+
false
303+
}
304+
}
305+
306+
fn has_bench_signature(cx: &ExtCtxt, i: &ast::Item) -> bool {
307+
let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node {
308+
// NB: inadequate check, but we're running
309+
// well before resolve, can't get too deep.
310+
decl.inputs.len() == 1
311+
} else {
312+
false
313+
};
314+
315+
if !has_sig {
316+
cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
317+
signature `fn(&mut Bencher) -> impl Termination`");
318+
}
319+
320+
has_sig
321+
}

‎src/libtest/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#![feature(panic_unwind)]
4141
#![feature(staged_api)]
4242
#![feature(termination_trait_lib)]
43+
#![feature(test)]
4344

4445
extern crate getopts;
4546
#[cfg(any(unix, target_os = "cloudabi"))]
@@ -301,7 +302,7 @@ pub fn test_main(args: &[String], tests: Vec<TestDescAndFn>, options: Options) {
301302
// a Vec<TestDescAndFn> is used in order to effect ownership-transfer
302303
// semantics into parallel test runners, which in turn requires a Vec<>
303304
// rather than a &[].
304-
pub fn test_main_static(tests: &[TestDescAndFn]) {
305+
pub fn test_main_static(tests: &[&mut TestDescAndFn]) {
305306
let args = env::args().collect::<Vec<_>>();
306307
let owned_tests = tests
307308
.iter()

‎src/libtest/stats.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,8 @@ mod tests {
907907

908908
#[cfg(test)]
909909
mod bench {
910-
use Bencher;
910+
extern crate test;
911+
use self::test::Bencher;
911912
use stats::Stats;
912913

913914
#[bench]

‎src/test/incremental/issue-49595/issue_49595.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@
1515
#![feature(rustc_attrs)]
1616
#![crate_type = "rlib"]
1717

18-
#![rustc_partition_codegened(module="issue_49595-__test", cfg="cfail2")]
18+
#![rustc_partition_codegened(module="issue_49595-tests", cfg="cfail2")]
1919
#![rustc_partition_codegened(module="issue_49595-lit_test", cfg="cfail3")]
2020

2121
mod tests {
22-
#[cfg_attr(not(cfail1), ignore)]
23-
#[test]
22+
#[cfg_attr(not(cfail1), test)]
2423
fn test() {
2524
}
2625
}

‎src/test/run-fail/test-panic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// except according to those terms.
1010

1111
// check-stdout
12-
// error-pattern:thread 'test_foo' panicked at
12+
// error-pattern:thread 'test_panic::test_foo' panicked at
1313
// compile-flags: --test
1414
// ignore-emscripten
1515

‎src/test/run-fail/test-should-fail-bad-message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// except according to those terms.
1010

1111
// check-stdout
12-
// error-pattern:thread 'test_foo' panicked at
12+
// error-pattern:thread 'test_should_fail_bad_message::test_foo' panicked at
1313
// compile-flags: --test
1414
// ignore-emscripten
1515

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{ "type": "suite", "event": "started", "test_count": "4" }
2-
{ "type": "test", "event": "started", "name": "a" }
3-
{ "type": "test", "name": "a", "event": "ok" }
4-
{ "type": "test", "event": "started", "name": "b" }
5-
{ "type": "test", "name": "b", "event": "failed", "stdout": "thread 'b' panicked at 'assertion failed: false', f.rs:18:5\nnote: Run with `RUST_BACKTRACE=1` for a backtrace.\n" }
6-
{ "type": "test", "event": "started", "name": "c" }
7-
{ "type": "test", "name": "c", "event": "ok" }
8-
{ "type": "test", "event": "started", "name": "d" }
9-
{ "type": "test", "name": "d", "event": "ignored" }
2+
{ "type": "test", "event": "started", "name": "f::a" }
3+
{ "type": "test", "name": "f::a", "event": "ok" }
4+
{ "type": "test", "event": "started", "name": "f::b" }
5+
{ "type": "test", "name": "f::b", "event": "failed", "stdout": "thread 'f::b' panicked at 'assertion failed: false', f.rs:18:5\nnote: Run with `RUST_BACKTRACE=1` for a backtrace.\n" }
6+
{ "type": "test", "event": "started", "name": "f::c" }
7+
{ "type": "test", "name": "f::c", "event": "ok" }
8+
{ "type": "test", "event": "started", "name": "f::d" }
9+
{ "type": "test", "name": "f::d", "event": "ignored" }
1010
{ "type": "suite", "event": "failed", "passed": 2, "failed": 1, "allowed_fail": 0, "ignored": 1, "measured": 0, "filtered_out": "0" }

‎src/test/ui/cfg-non-opt-expr.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// except according to those terms.
1010

1111
#![feature(stmt_expr_attributes)]
12+
#![feature(custom_test_frameworks)]
1213

1314
fn main() {
1415
let _ = #[cfg(unset)] ();
@@ -17,6 +18,6 @@ fn main() {
1718
//~^ ERROR removing an expression is not supported in this position
1819
let _ = [1, 2, 3][#[cfg(unset)] 1];
1920
//~^ ERROR removing an expression is not supported in this position
20-
let _ = #[test] ();
21+
let _ = #[test_case] ();
2122
//~^ ERROR removing an expression is not supported in this position
2223
}

‎src/test/ui/cfg-non-opt-expr.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
error: removing an expression is not supported in this position
2-
--> $DIR/cfg-non-opt-expr.rs:14:13
2+
--> $DIR/cfg-non-opt-expr.rs:15:13
33
|
44
LL | let _ = #[cfg(unset)] ();
55
| ^^^^^^^^^^^^^
66

77
error: removing an expression is not supported in this position
8-
--> $DIR/cfg-non-opt-expr.rs:16:21
8+
--> $DIR/cfg-non-opt-expr.rs:17:21
99
|
1010
LL | let _ = 1 + 2 + #[cfg(unset)] 3;
1111
| ^^^^^^^^^^^^^
1212

1313
error: removing an expression is not supported in this position
14-
--> $DIR/cfg-non-opt-expr.rs:18:23
14+
--> $DIR/cfg-non-opt-expr.rs:19:23
1515
|
1616
LL | let _ = [1, 2, 3][#[cfg(unset)] 1];
1717
| ^^^^^^^^^^^^^
1818

1919
error: removing an expression is not supported in this position
20-
--> $DIR/cfg-non-opt-expr.rs:20:13
20+
--> $DIR/cfg-non-opt-expr.rs:21:13
2121
|
22-
LL | let _ = #[test] ();
23-
| ^^^^^^^
22+
LL | let _ = #[test_case] ();
23+
| ^^^^^^^^^^^^
2424

2525
error: aborting due to 4 previous errors
2626

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// compile-flags: --test
12+
// run-pass
13+
14+
#![feature(custom_test_frameworks)]
15+
#![test_runner(crate::foo_runner)]
16+
17+
#[cfg(test)]
18+
fn foo_runner(ts: &[&Fn(usize)->()]) {
19+
for (i, t) in ts.iter().enumerate() {
20+
t(i);
21+
}
22+
}
23+
24+
#[test_case]
25+
fn test1(i: usize) {
26+
println!("Hi #{}", i);
27+
}
28+
29+
#[test_case]
30+
fn test2(i: usize) {
31+
println!("Hey #{}", i);
32+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature
12+
13+
fn main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0658]: Custom Test Frameworks is an unstable feature (see issue #50297)
2+
--> $DIR/feature-gate-custom_test_frameworks.rs:11:1
3+
|
4+
LL | #![test_runner(main)] //~ ERROR Custom Test Frameworks is an unstable feature
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: add #![feature(custom_test_frameworks)] to the crate attributes to enable
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0658`.

‎src/test/ui/inaccessible-test-modules.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error[E0432]: unresolved import `__test`
22
--> $DIR/inaccessible-test-modules.rs:15:5
33
|
44
LL | use __test as x; //~ ERROR unresolved import `__test`
5-
| ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `__test`?
5+
| ^^^^^^^^^^^ no `__test` in the root. Did you mean to use `test`?
66

77
error[E0432]: unresolved import `__test_reexports`
88
--> $DIR/inaccessible-test-modules.rs:16:5

‎src/test/ui/issues/issue-11692-2.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010

1111
fn main() {
1212
concat!(test!());
13-
//~^ ERROR cannot find macro `test!` in this scope
13+
//~^ error: `test` can only be used in attributes
1414
}

‎src/test/ui/issues/issue-11692-2.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: cannot find macro `test!` in this scope
1+
error: `test` can only be used in attributes
22
--> $DIR/issue-11692-2.rs:12:13
33
|
44
LL | concat!(test!());

‎src/test/ui/issues/issue-12997-2.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ LL | fn bar(x: isize) { }
55
| ^^^^^^^^^^^^^^^^^^^^ expected isize, found mutable reference
66
|
77
= note: expected type `isize`
8-
found type `&mut __test::test::Bencher`
8+
found type `&mut test::Bencher`
99

1010
error: aborting due to previous error
1111

‎src/test/ui/lint/test-inner-fn.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// compile-flags: --test -D unnameable_test_functions
11+
// compile-flags: --test -D unnameable_test_items
1212

1313
#[test]
1414
fn foo() {
15-
#[test] //~ ERROR cannot test inner function [unnameable_test_functions]
15+
#[test] //~ ERROR cannot test inner items [unnameable_test_items]
1616
fn bar() {}
1717
bar();
1818
}
1919

2020
mod x {
2121
#[test]
2222
fn foo() {
23-
#[test] //~ ERROR cannot test inner function [unnameable_test_functions]
23+
#[test] //~ ERROR cannot test inner items [unnameable_test_items]
2424
fn bar() {}
2525
bar();
2626
}

‎src/test/ui/lint/test-inner-fn.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
error: cannot test inner function
1+
error: cannot test inner items
22
--> $DIR/test-inner-fn.rs:15:5
33
|
4-
LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions]
4+
LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items]
55
| ^^^^^^^
66
|
7-
= note: requested on the command line with `-D unnameable-test-functions`
7+
= note: requested on the command line with `-D unnameable-test-items`
88

9-
error: cannot test inner function
9+
error: cannot test inner items
1010
--> $DIR/test-inner-fn.rs:23:9
1111
|
12-
LL | #[test] //~ ERROR cannot test inner function [unnameable_test_functions]
12+
LL | #[test] //~ ERROR cannot test inner items [unnameable_test_items]
1313
| ^^^^^^^
1414

1515
error: aborting due to 2 previous errors

‎src/test/ui/rfc-1937-termination-trait/termination-trait-test-wrong-type.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ LL | | }
77
| |_^ `main` can only return types that implement `std::process::Termination`
88
|
99
= help: the trait `std::process::Termination` is not implemented for `std::result::Result<f32, std::num::ParseIntError>`
10-
= note: required by `__test::test::assert_test_result`
10+
= note: required by `test::assert_test_result`
1111

1212
error: aborting due to previous error
1313

0 commit comments

Comments
 (0)
Please sign in to comment.