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::{Mark, SyntaxContext};
9 use syntax::print::pprust;
10 use syntax::symbol::{Symbol, sym};
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(sym::test),
69 allow_internal_unstable: Some(vec![sym::rustc_attrs, sym::test].into()),
70 allow_internal_unsafe: false,
71 local_inner_macros: false,
72 edition: cx.parse_sess.edition,
74 (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
75 attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
78 // Gensym "test" so we can extern crate without conflicting with any local names
79 let test_id = cx.ident_of("test").gensym();
81 // creates test::$name
82 let test_path = |name| {
83 cx.path(sp, vec![test_id, cx.ident_of(name)])
86 // creates test::ShouldPanic::$name
87 let should_panic_path = |name| {
88 cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)])
91 // creates $name: $expr
92 let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
94 let test_fn = if is_bench {
95 // A simple ident for a lambda
96 let b = cx.ident_of("b");
98 cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
99 // |b| self::test::assert_test_result(
101 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
102 // super::$test_fn(b)
104 cx.expr_path(cx.path(sp, vec![item.ident])),
105 vec![cx.expr_ident(sp, b)])
112 cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
115 // test::assert_test_result(
116 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
118 cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
127 let mut test_const = cx.item(sp, ast::Ident::new(item.ident.name, sp).gensym(),
130 cx.attribute(attr_sp, cx.meta_list(attr_sp, sym::cfg, vec![
131 cx.meta_list_item_word(attr_sp, sym::test)
133 // #[rustc_test_marker]
134 cx.attribute(attr_sp, cx.meta_word(attr_sp, sym::rustc_test_marker)),
136 // const $ident: test::TestDescAndFn =
137 ast::ItemKind::Const(cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
138 // test::TestDescAndFn {
139 cx.expr_struct(sp, test_path("TestDescAndFn"), vec![
140 // desc: test::TestDesc {
141 field("desc", cx.expr_struct(sp, test_path("TestDesc"), vec![
142 // name: "path::to::test"
143 field("name", cx.expr_call(sp, cx.expr_path(test_path("StaticTestName")),
145 cx.expr_str(sp, Symbol::intern(&item_path(
146 // skip the name of the root module
147 &cx.current_expansion.module.mod_path[1..],
151 // ignore: true | false
152 field("ignore", cx.expr_bool(sp, should_ignore(&item))),
153 // allow_fail: true | false
154 field("allow_fail", cx.expr_bool(sp, should_fail(&item))),
156 field("should_panic", match should_panic(cx, &item) {
157 // test::ShouldPanic::No
158 ShouldPanic::No => cx.expr_path(should_panic_path("No")),
159 // test::ShouldPanic::Yes
160 ShouldPanic::Yes(None) => cx.expr_path(should_panic_path("Yes")),
161 // test::ShouldPanic::YesWithMessage("...")
162 ShouldPanic::Yes(Some(sym)) => cx.expr_call(sp,
163 cx.expr_path(should_panic_path("YesWithMessage")),
164 vec![cx.expr_str(sp, sym)]),
168 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
169 field("testfn", test_fn)
174 test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
176 // extern crate test as test_gensym
177 let test_extern = cx.item(sp,
180 ast::ItemKind::ExternCrate(Some(sym::test))
183 log::debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
186 // Access to libtest under a gensymed name
187 Annotatable::Item(test_extern),
188 // The generated test case
189 Annotatable::Item(test_const),
191 Annotatable::Item(item)
195 fn item_path(mod_path: &[ast::Ident], item_ident: &ast::Ident) -> String {
196 mod_path.iter().chain(iter::once(item_ident))
197 .map(|x| x.to_string()).collect::<Vec<String>>().join("::")
205 fn should_ignore(i: &ast::Item) -> bool {
206 attr::contains_name(&i.attrs, sym::ignore)
209 fn should_fail(i: &ast::Item) -> bool {
210 attr::contains_name(&i.attrs, sym::allow_fail)
213 fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
214 match attr::find_by_name(&i.attrs, sym::should_panic) {
216 let ref sd = cx.parse_sess.span_diagnostic;
218 match attr.meta_item_list() {
219 // Handle #[should_panic(expected = "foo")]
221 let msg = list.iter()
222 .find(|mi| mi.check_name(sym::expected))
223 .and_then(|mi| mi.meta_item())
224 .and_then(|mi| mi.value_str());
225 if list.len() != 1 || msg.is_none() {
228 "argument must be of the form: \
229 `expected = \"error message\"`"
230 ).note("Errors in this attribute were erroneously \
231 allowed and will become a hard error in a \
232 future release.").emit();
233 ShouldPanic::Yes(None)
235 ShouldPanic::Yes(msg)
238 // Handle #[should_panic] and #[should_panic = "expected"]
239 None => ShouldPanic::Yes(attr.value_str())
242 None => ShouldPanic::No,
246 fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
247 let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
248 let ref sd = cx.parse_sess.span_diagnostic;
249 if let ast::ItemKind::Fn(ref decl, ref header, ref generics, _) = i.node {
250 if header.unsafety == ast::Unsafety::Unsafe {
253 "unsafe functions cannot be used for tests"
257 if header.asyncness.node.is_async() {
260 "async functions cannot be used for tests"
266 // If the termination trait is active, the compiler will check that the output
267 // type implements the `Termination` trait as `libtest` enforces that.
268 let has_output = match decl.output {
269 ast::FunctionRetTy::Default(..) => false,
270 ast::FunctionRetTy::Ty(ref t) if t.node.is_unit() => false,
274 if !decl.inputs.is_empty() {
275 sd.span_err(i.span, "functions used as tests can not have any arguments");
279 match (has_output, has_should_panic_attr) {
281 sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
284 (true, false) => if !generics.params.is_empty() {
286 "functions used as tests must have signature fn() -> ()");
294 sd.span_err(i.span, "only functions may be used as tests");
299 fn has_bench_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
300 let has_sig = if let ast::ItemKind::Fn(ref decl, _, _, _) = i.node {
301 // N.B., inadequate check, but we're running
302 // well before resolve, can't get too deep.
303 decl.inputs.len() == 1
309 cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
310 signature `fn(&mut Bencher) -> impl Termination`");