1 /// The expansion from a test function to the appropriate test struct for libtest
2 /// Ideally, this code would be in libtest but for efficiency and error messages it lives here.
4 use syntax::ext::base::*;
5 use syntax::ext::build::AstBuilder;
6 use syntax::ext::hygiene::{self, Mark, SyntaxContext};
9 use syntax::print::pprust;
10 use syntax::symbol::Symbol;
11 use syntax_pos::{DUMMY_SP, Span};
12 use syntax::source_map::{ExpnInfo, MacroAttribute};
18 _meta_item: &ast::MetaItem,
20 ) -> Vec<Annotatable> {
21 expand_test_or_bench(cx, attr_sp, item, false)
27 _meta_item: &ast::MetaItem,
29 ) -> Vec<Annotatable> {
30 expand_test_or_bench(cx, attr_sp, item, true)
33 pub fn expand_test_or_bench(
38 ) -> Vec<Annotatable> {
39 // If we're not in test configuration, remove the annotated item
40 if !cx.ecfg.should_test { return vec![]; }
43 if let Annotatable::Item(i) = item { i }
45 cx.parse_sess.span_diagnostic.span_fatal(item.span(),
46 "#[test] attribute is only allowed on non associated functions").raise();
49 if let ast::ItemKind::Mac(_) = item.node {
50 cx.parse_sess.span_diagnostic.span_warn(item.span,
51 "#[test] attribute should not be used on macros. Use #[cfg(test)] instead.");
52 return vec![Annotatable::Item(item)];
55 // has_*_signature will report any errors in the type so compilation
56 // will fail. We shouldn't try to expand in this case because the errors
58 if (!is_bench && !has_test_signature(cx, &item)) ||
59 (is_bench && !has_bench_signature(cx, &item)) {
60 return vec![Annotatable::Item(item)];
64 let mark = Mark::fresh(Mark::root());
65 mark.set_expn_info(ExpnInfo {
68 format: MacroAttribute(Symbol::intern("test")),
69 allow_internal_unstable: Some(vec![
70 Symbol::intern("rustc_attrs"),
71 Symbol::intern("test"),
73 allow_internal_unsafe: false,
74 local_inner_macros: false,
75 edition: hygiene::default_edition(),
77 (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
78 attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
81 // Gensym "test" so we can extern crate without conflicting with any local names
82 let test_id = cx.ident_of("test").gensym();
84 // creates test::$name
85 let test_path = |name| {
86 cx.path(sp, vec![test_id, cx.ident_of(name)])
89 // creates test::$name
90 let should_panic_path = |name| {
91 cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)])
94 // creates $name: $expr
95 let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
97 let test_fn = if is_bench {
98 // A simple ident for a lambda
99 let b = cx.ident_of("b");
101 cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
102 // |b| self::test::assert_test_result(
104 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
105 // super::$test_fn(b)
107 cx.expr_path(cx.path(sp, vec![item.ident])),
108 vec![cx.expr_ident(sp, b)])
115 cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
118 // test::assert_test_result(
119 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
121 cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
130 let mut test_const = cx.item(sp, ast::Ident::new(item.ident.name.gensymed(), sp),
133 cx.attribute(attr_sp, cx.meta_list(attr_sp, Symbol::intern("cfg"), vec![
134 cx.meta_list_item_word(attr_sp, Symbol::intern("test"))
136 // #[rustc_test_marker]
137 cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker"))),
139 // const $ident: test::TestDescAndFn =
140 ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
141 // test::TestDescAndFn {
142 cx.expr_struct(sp, test_path("TestDescAndFn"), vec![
143 // desc: test::TestDesc {
144 field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![
145 // name: "path::to::test"
146 field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")),
148 cx.expr_str(sp, Symbol::intern(&item_path(
149 // skip the name of the root module
150 &cx.current_expansion.module.mod_path[1..],
154 // ignore: true | false
155 field("ignore", cx.expr_bool(sp, should_ignore(&item))),
156 // allow_fail: true | false
157 field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
159 field("should_panic", match should_panic(cx, &item) {
160 // test::ShouldPanic::No
161 ShouldPanic::No => cx.expr_path(should_panic_path("No")),
162 // test::ShouldPanic::Yes
163 ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")),
164 // test::ShouldPanic::YesWithMessage("...")
165 ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp,
166 cx.expr_path(should_panic_path("YesWithMessage")),
167 vec![cx.expr_str(sp, sym)]),
171 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
172 field("testfn", test_fn)
177 test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
179 // extern crate test as test_gensym
180 let test_extern = cx.item(sp,
183 ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
186 log::debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
189 // Access to libtest under a gensymed name
190 Annotatable::Item(test_extern),
191 // The generated test case
192 Annotatable::Item(test_const),
194 Annotatable::Item(item)
198 fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String {
199 mod_path.iter().chain(iter::once(item_ident))
200 .map(|x| x.to_string()).collect::<Vec<String>>().join("::")
208 fn should_ignore(i: &ast::Item) -> bool {
209 attr::contains_name(&i.attrs, "ignore")
212 fn should_fail(i: &ast::Item) -> bool {
213 attr::contains_name(&i.attrs, "allow_fail")
216 fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
217 match attr::find_by_name(&i.attrs, "should_panic") {
219 let ref sd = cx.parse_sess.span_diagnostic;
221 match attr.meta_item_list() {
222 // Handle #[should_panic(expected = "foo")]
224 let msg = list.iter()
225 .find(|mi| mi.check_name("expected"))
226 .and_then(|mi| mi.meta_item())
227 .and_then(|mi| mi.value_str());
228 if list.len() != 1 || msg.is_none() {
231 "argument must be of the form: \
232 `expected = \"error message\"`"
233 ).note("Errors in this attribute were erroneously \
234 allowed and will become a hard error in a \
235 future release.").emit();
236 ShouldPanic::Yes(None)
238 ShouldPanic::Yes(msg)
241 // Handle #[should_panic] and #[should_panic = "expected"]
242 None => ShouldPanic::Yes(attr.value_str())
245 None => ShouldPanic::No,
249 fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
250 let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic");
251 let ref sd = cx.parse_sess.span_diagnostic;
252 if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node {
253 if header.unsafety == ast::Unsafety::Unsafe {
256 "unsafe functions cannot be used for tests"
260 if header.asyncness.is_async() {
263 "async functions cannot be used for tests"
269 // If the termination trait is active, the compiler will check that the output
270 // type implements the `Termination` trait as `libtest` enforces that.
271 let has_output = match decl.output {
272 ast::FunctionRetTy::Default(..) => false,
273 ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false,
277 if !decl.inputs.is_empty() {
278 sd.span_err(i.span, "functions used as tests can not have any arguments");
282 match (has_output, has_should_panic_attr) {
284 sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
287 (true, false) => if !generics.params.is_empty() {
289 "functions used as tests must have signature fn() -> ()");
297 sd.span_err(i.span, "only functions may be used as tests");
302 fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
303 let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node {
304 // N.B., inadequate check, but we're running
305 // well before resolve, can't get too deep.
306 decl.inputs.len() == 1
312 cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
313 signature `fn(&mut Bencher) -> impl Termination`");