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.
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.
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.
14 use syntax::ext::base::*;
15 use syntax::ext::build::AstBuilder;
16 use syntax::ext::hygiene::{self, Mark, SyntaxContext};
19 use syntax::print::pprust;
20 use syntax::symbol::Symbol;
21 use syntax_pos::{DUMMY_SP, Span};
22 use syntax::source_map::{ExpnInfo, MacroAttribute};
28 _meta_item: &ast::MetaItem,
30 ) -> Vec<Annotatable> {
31 expand_test_or_bench(cx, attr_sp, item, false)
37 _meta_item: &ast::MetaItem,
39 ) -> Vec<Annotatable> {
40 expand_test_or_bench(cx, attr_sp, item, true)
43 pub fn expand_test_or_bench(
48 ) -> Vec<Annotatable> {
49 // If we're not in test configuration, remove the annotated item
50 if !cx.ecfg.should_test { return vec![]; }
53 if let Annotatable::Item(i) = item { i }
55 cx.parse_sess.span_diagnostic.span_fatal(item.span(),
56 "#[test] attribute is only allowed on fn items").raise();
59 if let ast::ItemKind::Mac(_) = item.node {
60 cx.parse_sess.span_diagnostic.span_warn(item.span,
61 "#[test] attribute should not be used on macros. Use #[cfg(test)] instead.");
62 return vec![Annotatable::Item(item)];
65 // has_*_signature will report any errors in the type so compilation
66 // will fail. We shouldn't try to expand in this case because the errors
68 if (!is_bench && !has_test_signature(cx, &item)) ||
69 (is_bench && !has_bench_signature(cx, &item)) {
70 return vec![Annotatable::Item(item)];
74 let mark = Mark::fresh(Mark::root());
75 mark.set_expn_info(ExpnInfo {
78 format: MacroAttribute(Symbol::intern("test")),
79 allow_internal_unstable: true,
80 allow_internal_unsafe: false,
81 local_inner_macros: false,
82 edition: hygiene::default_edition(),
84 (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
85 attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
88 // Gensym "test" so we can extern crate without conflicting with any local names
89 let test_id = cx.ident_of("test").gensym();
91 // creates test::$name
92 let test_path = |name| {
93 cx.path(sp, vec![test_id, cx.ident_of(name)])
96 // creates test::$name
97 let should_panic_path = |name| {
98 cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)])
101 // creates $name: $expr
102 let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
104 let test_fn = if is_bench {
105 // A simple ident for a lambda
106 let b = cx.ident_of("b");
108 cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
109 // |b| self::test::assert_test_result(
111 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
112 // super::$test_fn(b)
114 cx.expr_path(cx.path(sp, vec![item.ident])),
115 vec![cx.expr_ident(sp, b)])
122 cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
125 // test::assert_test_result(
126 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
128 cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
137 let mut test_const = cx.item(sp, item.ident.gensym(),
140 cx.attribute(attr_sp, cx.meta_list(attr_sp, Symbol::intern("cfg"), vec![
141 cx.meta_list_item_word(attr_sp, Symbol::intern("test"))
143 // #[rustc_test_marker]
144 cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker")))
146 // const $ident: test::TestDescAndFn =
147 ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
148 // test::TestDescAndFn {
149 cx.expr_struct(sp, test_path("TestDescAndFn"), vec![
150 // desc: test::TestDesc {
151 field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![
152 // name: "path::to::test"
153 field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")),
155 cx.expr_str(sp, Symbol::intern(&item_path(
156 // skip the name of the root module
157 &cx.current_expansion.module.mod_path[1..],
161 // ignore: true | false
162 field("ignore", cx.expr_bool(sp, should_ignore(&item))),
163 // allow_fail: true | false
164 field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
166 field("should_panic", match should_panic(cx, &item) {
167 // test::ShouldPanic::No
168 ShouldPanic::No => cx.expr_path(should_panic_path("No")),
169 // test::ShouldPanic::Yes
170 ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")),
171 // test::ShouldPanic::YesWithMessage("...")
172 ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp,
173 cx.expr_path(should_panic_path("YesWithMessage")),
174 vec![cx.expr_str(sp, sym)]),
178 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
179 field("testfn", test_fn)
184 test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
186 // extern crate test as test_gensym
187 let test_extern = cx.item(sp,
190 ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
193 debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
196 // Access to libtest under a gensymed name
197 Annotatable::Item(test_extern),
198 // The generated test case
199 Annotatable::Item(test_const),
201 Annotatable::Item(item)
205 fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String {
206 mod_path.iter().chain(iter::once(item_ident))
207 .map(|x| x.to_string()).collect::<Vec<String>>().join("::")
215 fn should_ignore(i: &ast::Item) -> bool {
216 attr::contains_name(&i.attrs, "ignore")
219 fn should_fail(i: &ast::Item) -> bool {
220 attr::contains_name(&i.attrs, "allow_fail")
223 fn should_panic(cx: &ExtCtxt, i: &ast::Item) -> ShouldPanic {
224 match attr::find_by_name(&i.attrs, "should_panic") {
226 let ref sd = cx.parse_sess.span_diagnostic;
227 if attr.is_value_str() {
230 "attribute must be of the form: \
231 `#[should_panic]` or \
232 `#[should_panic(expected = \"error message\")]`"
233 ).note("Errors in this attribute were erroneously allowed \
234 and will become a hard error in a future release.")
236 return ShouldPanic::Yes(None);
238 match attr.meta_item_list() {
239 // Handle #[should_panic]
240 None => ShouldPanic::Yes(None),
241 // Handle #[should_panic(expected = "foo")]
243 let msg = list.iter()
244 .find(|mi| mi.check_name("expected"))
245 .and_then(|mi| mi.meta_item())
246 .and_then(|mi| mi.value_str());
247 if list.len() != 1 || msg.is_none() {
250 "argument must be of the form: \
251 `expected = \"error message\"`"
252 ).note("Errors in this attribute were erroneously \
253 allowed and will become a hard error in a \
254 future release.").emit();
255 ShouldPanic::Yes(None)
257 ShouldPanic::Yes(msg)
262 None => ShouldPanic::No,
266 fn has_test_signature(cx: &ExtCtxt, i: &ast::Item) -> bool {
267 let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic");
268 let ref sd = cx.parse_sess.span_diagnostic;
269 if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node {
270 if header.unsafety == ast::Unsafety::Unsafe {
273 "unsafe functions cannot be used for tests"
277 if header.asyncness.is_async() {
280 "async functions cannot be used for tests"
286 // If the termination trait is active, the compiler will check that the output
287 // type implements the `Termination` trait as `libtest` enforces that.
288 let has_output = match decl.output {
289 ast::FunctionRetTy::Default(..) => false,
290 ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false,
294 if !decl.inputs.is_empty() {
295 sd.span_err(i.span, "functions used as tests can not have any arguments");
299 match (has_output, has_should_panic_attr) {
301 sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
304 (true, false) => if !generics.params.is_empty() {
306 "functions used as tests must have signature fn() -> ()");
314 sd.span_err(i.span, "only functions may be used as tests");
319 fn has_bench_signature(cx: &ExtCtxt, i: &ast::Item) -> bool {
320 let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node {
321 // NB: inadequate check, but we're running
322 // well before resolve, can't get too deep.
323 decl.inputs.len() == 1
329 cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
330 signature `fn(&mut Bencher) -> impl Termination`");