]> git.lizzy.rs Git - rust.git/blob - src/libsyntax_ext/test.rs
Auto merge of #60763 - matklad:tt-parser, r=petrochenkov
[rust.git] / src / libsyntax_ext / test.rs
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.
3
4 use syntax::ext::base::*;
5 use syntax::ext::build::AstBuilder;
6 use syntax::ext::hygiene::{self, Mark, SyntaxContext};
7 use syntax::attr;
8 use syntax::ast;
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};
13 use std::iter;
14
15 pub fn expand_test(
16     cx: &mut ExtCtxt<'_>,
17     attr_sp: Span,
18     _meta_item: &ast::MetaItem,
19     item: Annotatable,
20 ) -> Vec<Annotatable> {
21     expand_test_or_bench(cx, attr_sp, item, false)
22 }
23
24 pub fn expand_bench(
25     cx: &mut ExtCtxt<'_>,
26     attr_sp: Span,
27     _meta_item: &ast::MetaItem,
28     item: Annotatable,
29 ) -> Vec<Annotatable> {
30     expand_test_or_bench(cx, attr_sp, item, true)
31 }
32
33 pub fn expand_test_or_bench(
34     cx: &mut ExtCtxt<'_>,
35     attr_sp: Span,
36     item: Annotatable,
37     is_bench: bool
38 ) -> Vec<Annotatable> {
39     // If we're not in test configuration, remove the annotated item
40     if !cx.ecfg.should_test { return vec![]; }
41
42     let item =
43         if let Annotatable::Item(i) = item { i }
44         else {
45             cx.parse_sess.span_diagnostic.span_fatal(item.span(),
46                 "#[test] attribute is only allowed on non associated functions").raise();
47         };
48
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)];
53     }
54
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
57     // would be spurious.
58     if (!is_bench && !has_test_signature(cx, &item)) ||
59         (is_bench && !has_bench_signature(cx, &item)) {
60         return vec![Annotatable::Item(item)];
61     }
62
63     let (sp, attr_sp) = {
64         let mark = Mark::fresh(Mark::root());
65         mark.set_expn_info(ExpnInfo {
66             call_site: DUMMY_SP,
67             def_site: None,
68             format: MacroAttribute(Symbol::intern("test")),
69             allow_internal_unstable: Some(vec![
70                 Symbol::intern("rustc_attrs"),
71                 Symbol::intern("test"),
72             ].into()),
73             allow_internal_unsafe: false,
74             local_inner_macros: false,
75             edition: hygiene::default_edition(),
76         });
77         (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
78          attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
79     };
80
81     // Gensym "test" so we can extern crate without conflicting with any local names
82     let test_id = cx.ident_of("test").gensym();
83
84     // creates test::$name
85     let test_path = |name| {
86         cx.path(sp, vec![test_id, cx.ident_of(name)])
87     };
88
89     // creates test::ShouldPanic::$name
90     let should_panic_path = |name| {
91         cx.path(sp, vec![test_id, cx.ident_of("ShouldPanic"), cx.ident_of(name)])
92     };
93
94     // creates $name: $expr
95     let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
96
97     let test_fn = if is_bench {
98         // A simple ident for a lambda
99         let b = cx.ident_of("b");
100
101         cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
102             // |b| self::test::assert_test_result(
103             cx.lambda1(sp,
104                 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
105                     // super::$test_fn(b)
106                     cx.expr_call(sp,
107                         cx.expr_path(cx.path(sp, vec![item.ident])),
108                         vec![cx.expr_ident(sp, b)])
109                 ]),
110                 b
111             )
112             // )
113         ])
114     } else {
115         cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
116             // || {
117             cx.lambda0(sp,
118                 // test::assert_test_result(
119                 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
120                     // $test_fn()
121                     cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
122                 // )
123                 ])
124             // }
125             )
126         // )
127         ])
128     };
129
130     let mut test_const = cx.item(sp, ast::Ident::new(item.ident.name.gensymed(), sp),
131         vec![
132             // #[cfg(test)]
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"))
135             ])),
136             // #[rustc_test_marker]
137             cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker"))),
138         ],
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")),
147                         vec![
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..],
151                                 &item.ident
152                             )))
153                         ])),
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))),
158                     // should_panic: ...
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)]),
168                     }),
169                 // },
170                 ])),
171                 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
172                 field("testfn", test_fn)
173             // }
174             ])
175         // }
176         ));
177     test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
178
179     // extern crate test as test_gensym
180     let test_extern = cx.item(sp,
181         test_id,
182         vec![],
183         ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
184     );
185
186     log::debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
187
188     vec![
189         // Access to libtest under a gensymed name
190         Annotatable::Item(test_extern),
191         // The generated test case
192         Annotatable::Item(test_const),
193         // The original item
194         Annotatable::Item(item)
195     ]
196 }
197
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("::")
201 }
202
203 enum ShouldPanic {
204     No,
205     Yes(Option<Symbol>),
206 }
207
208 fn should_ignore(i: &ast::Item) -> bool {
209     attr::contains_name(&i.attrs, sym::ignore)
210 }
211
212 fn should_fail(i: &ast::Item) -> bool {
213     attr::contains_name(&i.attrs, sym::allow_fail)
214 }
215
216 fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
217     match attr::find_by_name(&i.attrs, sym::should_panic) {
218         Some(attr) => {
219             let ref sd = cx.parse_sess.span_diagnostic;
220
221             match attr.meta_item_list() {
222                 // Handle #[should_panic(expected = "foo")]
223                 Some(list) => {
224                     let msg = list.iter()
225                         .find(|mi| mi.check_name(sym::expected))
226                         .and_then(|mi| mi.meta_item())
227                         .and_then(|mi| mi.value_str());
228                     if list.len() != 1 || msg.is_none() {
229                         sd.struct_span_warn(
230                             attr.span,
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)
237                     } else {
238                         ShouldPanic::Yes(msg)
239                     }
240                 },
241                 // Handle #[should_panic] and #[should_panic = "expected"]
242                 None => ShouldPanic::Yes(attr.value_str())
243             }
244         }
245         None => ShouldPanic::No,
246     }
247 }
248
249 fn has_test_signature(cx: &ExtCtxt<'_>, i: &ast::Item) -> bool {
250     let has_should_panic_attr = attr::contains_name(&i.attrs, sym::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 {
254             sd.span_err(
255                 i.span,
256                 "unsafe functions cannot be used for tests"
257             );
258             return false
259         }
260         if header.asyncness.node.is_async() {
261             sd.span_err(
262                 i.span,
263                 "async functions cannot be used for tests"
264             );
265             return false
266         }
267
268
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,
274             _ => true
275         };
276
277         if !decl.inputs.is_empty() {
278             sd.span_err(i.span, "functions used as tests can not have any arguments");
279             return false;
280         }
281
282         match (has_output, has_should_panic_attr) {
283             (true, true) => {
284                 sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
285                 false
286             },
287             (true, false) => if !generics.params.is_empty() {
288                 sd.span_err(i.span,
289                                 "functions used as tests must have signature fn() -> ()");
290                 false
291             } else {
292                 true
293             },
294             (false, _) => true
295         }
296     } else {
297         sd.span_err(i.span, "only functions may be used as tests");
298         false
299     }
300 }
301
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
307     } else {
308         false
309     };
310
311     if !has_sig {
312         cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
313             signature `fn(&mut Bencher) -> impl Termination`");
314     }
315
316     has_sig
317 }