]> git.lizzy.rs Git - rust.git/blob - src/libsyntax_ext/test.rs
Auto merge of #54813 - petrochenkov:uilocale, r=alexcrichton
[rust.git] / src / libsyntax_ext / test.rs
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     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)];
63     }
64
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
67     // would be spurious.
68     if (!is_bench && !has_test_signature(cx, &item)) ||
69         (is_bench && !has_bench_signature(cx, &item)) {
70         return vec![Annotatable::Item(item)];
71     }
72
73     let (sp, attr_sp) = {
74         let mark = Mark::fresh(Mark::root());
75         mark.set_expn_info(ExpnInfo {
76             call_site: DUMMY_SP,
77             def_site: None,
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(),
83         });
84         (item.span.with_ctxt(SyntaxContext::empty().apply_mark(mark)),
85          attr_sp.with_ctxt(SyntaxContext::empty().apply_mark(mark)))
86     };
87
88     // Gensym "test" so we can extern crate without conflicting with any local names
89     let test_id = cx.ident_of("test").gensym();
90
91     // creates test::$name
92     let test_path = |name| {
93         cx.path(sp, vec![test_id, cx.ident_of(name)])
94     };
95
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)])
99     };
100
101     // creates $name: $expr
102     let field = |name, expr| cx.field_imm(sp, cx.ident_of(name), expr);
103
104     let test_fn = if is_bench {
105         // A simple ident for a lambda
106         let b = cx.ident_of("b");
107
108         cx.expr_call(sp, cx.expr_path(test_path("StaticBenchFn")), vec![
109             // |b| self::test::assert_test_result(
110             cx.lambda1(sp,
111                 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
112                     // super::$test_fn(b)
113                     cx.expr_call(sp,
114                         cx.expr_path(cx.path(sp, vec![item.ident])),
115                         vec![cx.expr_ident(sp, b)])
116                 ]),
117                 b
118             )
119             // )
120         ])
121     } else {
122         cx.expr_call(sp, cx.expr_path(test_path("StaticTestFn")), vec![
123             // || {
124             cx.lambda0(sp,
125                 // test::assert_test_result(
126                 cx.expr_call(sp, cx.expr_path(test_path("assert_test_result")), vec![
127                     // $test_fn()
128                     cx.expr_call(sp, cx.expr_path(cx.path(sp, vec![item.ident])), vec![])
129                 // )
130                 ])
131             // }
132             )
133         // )
134         ])
135     };
136
137     let mut test_const = cx.item(sp, item.ident.gensym(),
138         vec![
139             // #[cfg(test)]
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"))
142             ])),
143             // #[rustc_test_marker]
144             cx.attribute(attr_sp, cx.meta_word(attr_sp, Symbol::intern("rustc_test_marker")))
145         ],
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")),
154                         vec![
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..],
158                                 &item.ident
159                             )))
160                         ])),
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))),
165                     // should_panic: ...
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)]),
175                     }),
176                 // },
177                 ])),
178                 // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
179                 field("testfn", test_fn)
180             // }
181             ])
182         // }
183         ));
184     test_const = test_const.map(|mut tc| { tc.vis.node = ast::VisibilityKind::Public; tc});
185
186     // extern crate test as test_gensym
187     let test_extern = cx.item(sp,
188         test_id,
189         vec![],
190         ast::ItemKind::ExternCrate(Some(Symbol::intern("test")))
191     );
192
193     debug!("Synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
194
195     vec![
196         // Access to libtest under a gensymed name
197         Annotatable::Item(test_extern),
198         // The generated test case
199         Annotatable::Item(test_const),
200         // The original item
201         Annotatable::Item(item)
202     ]
203 }
204
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("::")
208 }
209
210 enum ShouldPanic {
211     No,
212     Yes(Option<Symbol>),
213 }
214
215 fn should_ignore(i: &ast::Item) -> bool {
216     attr::contains_name(&i.attrs, "ignore")
217 }
218
219 fn should_fail(i: &ast::Item) -> bool {
220     attr::contains_name(&i.attrs, "allow_fail")
221 }
222
223 fn should_panic(cx: &ExtCtxt, i: &ast::Item) -> ShouldPanic {
224     match attr::find_by_name(&i.attrs, "should_panic") {
225         Some(attr) => {
226             let ref sd = cx.parse_sess.span_diagnostic;
227             if attr.is_value_str() {
228                 sd.struct_span_warn(
229                     attr.span(),
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.")
235                 .emit();
236                 return ShouldPanic::Yes(None);
237             }
238             match attr.meta_item_list() {
239                 // Handle #[should_panic]
240                 None => ShouldPanic::Yes(None),
241                 // Handle #[should_panic(expected = "foo")]
242                 Some(list) => {
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() {
248                         sd.struct_span_warn(
249                             attr.span(),
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)
256                     } else {
257                         ShouldPanic::Yes(msg)
258                     }
259                 },
260             }
261         }
262         None => ShouldPanic::No,
263     }
264 }
265
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 {
271             sd.span_err(
272                 i.span,
273                 "unsafe functions cannot be used for tests"
274             );
275             return false
276         }
277         if header.asyncness.is_async() {
278             sd.span_err(
279                 i.span,
280                 "async functions cannot be used for tests"
281             );
282             return false
283         }
284
285
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,
291             _ => true
292         };
293
294         if !decl.inputs.is_empty() {
295             sd.span_err(i.span, "functions used as tests can not have any arguments");
296             return false;
297         }
298
299         match (has_output, has_should_panic_attr) {
300             (true, true) => {
301                 sd.span_err(i.span, "functions using `#[should_panic]` must return `()`");
302                 false
303             },
304             (true, false) => if !generics.params.is_empty() {
305                 sd.span_err(i.span,
306                                 "functions used as tests must have signature fn() -> ()");
307                 false
308             } else {
309                 true
310             },
311             (false, _) => true
312         }
313     } else {
314         sd.span_err(i.span, "only functions may be used as tests");
315         false
316     }
317 }
318
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
324     } else {
325         false
326     };
327
328     if !has_sig {
329         cx.parse_sess.span_diagnostic.span_err(i.span, "functions used as benches must have \
330             signature `fn(&mut Bencher) -> impl Termination`");
331     }
332
333     has_sig
334 }