]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/test.rs
b4e1dd75b3b717b6d9bd7a0acec960e2f905f05b
[rust.git] / src / libsyntax / test.rs
1 // Copyright 2012-2014 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 // Code that generates a test runner to run all the tests in a crate
12
13 #![allow(dead_code)]
14 #![allow(unused_imports)]
15
16 use self::HasTestSignature::*;
17
18 use std::iter;
19 use std::slice;
20 use std::mem;
21 use std::vec;
22 use attr::{self, HasAttrs};
23 use syntax_pos::{self, DUMMY_SP, NO_EXPANSION, Span, FileMap, BytePos};
24
25 use codemap::{self, CodeMap, ExpnInfo, MacroAttribute, dummy_spanned};
26 use errors;
27 use config;
28 use entry::{self, EntryPointType};
29 use ext::base::{ExtCtxt, Resolver};
30 use ext::build::AstBuilder;
31 use ext::expand::ExpansionConfig;
32 use ext::hygiene::{self, Mark, SyntaxContext};
33 use fold::Folder;
34 use feature_gate::Features;
35 use util::move_map::MoveMap;
36 use fold;
37 use parse::{token, ParseSess};
38 use print::pprust;
39 use ast::{self, Ident};
40 use ptr::P;
41 use symbol::{self, Symbol, keywords};
42 use util::small_vector::SmallVector;
43
44 enum ShouldPanic {
45     No,
46     Yes(Option<Symbol>),
47 }
48
49 struct Test {
50     span: Span,
51     path: Vec<Ident> ,
52     bench: bool,
53     ignore: bool,
54     should_panic: ShouldPanic,
55     allow_fail: bool,
56 }
57
58 struct TestCtxt<'a> {
59     span_diagnostic: &'a errors::Handler,
60     path: Vec<Ident>,
61     ext_cx: ExtCtxt<'a>,
62     testfns: Vec<Test>,
63     reexport_test_harness_main: Option<Symbol>,
64     is_libtest: bool,
65     ctxt: SyntaxContext,
66     features: &'a Features,
67
68     // top-level re-export submodule, filled out after folding is finished
69     toplevel_reexport: Option<Ident>,
70 }
71
72 // Traverse the crate, collecting all the test functions, eliding any
73 // existing main functions, and synthesizing a main test harness
74 pub fn modify_for_testing(sess: &ParseSess,
75                           resolver: &mut dyn Resolver,
76                           should_test: bool,
77                           krate: ast::Crate,
78                           span_diagnostic: &errors::Handler,
79                           features: &Features) -> ast::Crate {
80     // Check for #[reexport_test_harness_main = "some_name"] which
81     // creates a `use __test::main as some_name;`. This needs to be
82     // unconditional, so that the attribute is still marked as used in
83     // non-test builds.
84     let reexport_test_harness_main =
85         attr::first_attr_value_str_by_name(&krate.attrs,
86                                            "reexport_test_harness_main");
87
88     if should_test {
89         generate_test_harness(sess, resolver, reexport_test_harness_main,
90                               krate, span_diagnostic, features)
91     } else {
92         krate
93     }
94 }
95
96 struct TestHarnessGenerator<'a> {
97     cx: TestCtxt<'a>,
98     tests: Vec<Ident>,
99
100     // submodule name, gensym'd identifier for re-exports
101     tested_submods: Vec<(Ident, Ident)>,
102 }
103
104 impl<'a> fold::Folder for TestHarnessGenerator<'a> {
105     fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate {
106         let mut folded = fold::noop_fold_crate(c, self);
107
108         // Add a special __test module to the crate that will contain code
109         // generated for the test harness
110         let (mod_, reexport) = mk_test_module(&mut self.cx);
111         if let Some(re) = reexport {
112             folded.module.items.push(re)
113         }
114         folded.module.items.push(mod_);
115         folded
116     }
117
118     fn fold_item(&mut self, i: P<ast::Item>) -> SmallVector<P<ast::Item>> {
119         let ident = i.ident;
120         if ident.name != keywords::Invalid.name() {
121             self.cx.path.push(ident);
122         }
123         debug!("current path: {}", path_name_i(&self.cx.path));
124
125         if is_test_fn(&self.cx, &i) || is_bench_fn(&self.cx, &i) {
126             match i.node {
127                 ast::ItemKind::Fn(_, header, _, _) => {
128                     if header.unsafety == ast::Unsafety::Unsafe {
129                         let diag = self.cx.span_diagnostic;
130                         diag.span_fatal(
131                             i.span,
132                             "unsafe functions cannot be used for tests"
133                         ).raise();
134                     }
135                     if header.asyncness.is_async() {
136                         let diag = self.cx.span_diagnostic;
137                         diag.span_fatal(
138                             i.span,
139                             "async functions cannot be used for tests"
140                         ).raise();
141                     }
142                 }
143                 _ => {},
144             }
145
146             debug!("this is a test function");
147             let test = Test {
148                 span: i.span,
149                 path: self.cx.path.clone(),
150                 bench: is_bench_fn(&self.cx, &i),
151                 ignore: is_ignored(&i),
152                 should_panic: should_panic(&i, &self.cx),
153                 allow_fail: is_allowed_fail(&i),
154             };
155             self.cx.testfns.push(test);
156             self.tests.push(i.ident);
157         }
158
159         let mut item = i.into_inner();
160         // We don't want to recurse into anything other than mods, since
161         // mods or tests inside of functions will break things
162         if let ast::ItemKind::Mod(module) = item.node {
163             let tests = mem::replace(&mut self.tests, Vec::new());
164             let tested_submods = mem::replace(&mut self.tested_submods, Vec::new());
165             let mut mod_folded = fold::noop_fold_mod(module, self);
166             let tests = mem::replace(&mut self.tests, tests);
167             let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
168
169             if !tests.is_empty() || !tested_submods.is_empty() {
170                 let (it, sym) = mk_reexport_mod(&mut self.cx, item.id, tests, tested_submods);
171                 mod_folded.items.push(it);
172
173                 if !self.cx.path.is_empty() {
174                     self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym));
175                 } else {
176                     debug!("pushing nothing, sym: {:?}", sym);
177                     self.cx.toplevel_reexport = Some(sym);
178                 }
179             }
180             item.node = ast::ItemKind::Mod(mod_folded);
181         }
182         if ident.name != keywords::Invalid.name() {
183             self.cx.path.pop();
184         }
185         SmallVector::one(P(item))
186     }
187
188     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
189 }
190
191 struct EntryPointCleaner {
192     // Current depth in the ast
193     depth: usize,
194 }
195
196 impl fold::Folder for EntryPointCleaner {
197     fn fold_item(&mut self, i: P<ast::Item>) -> SmallVector<P<ast::Item>> {
198         self.depth += 1;
199         let folded = fold::noop_fold_item(i, self).expect_one("noop did something");
200         self.depth -= 1;
201
202         // Remove any #[main] or #[start] from the AST so it doesn't
203         // clash with the one we're going to add, but mark it as
204         // #[allow(dead_code)] to avoid printing warnings.
205         let folded = match entry::entry_point_type(&folded, self.depth) {
206             EntryPointType::MainNamed |
207             EntryPointType::MainAttr |
208             EntryPointType::Start =>
209                 folded.map(|ast::Item {id, ident, attrs, node, vis, span, tokens}| {
210                     let allow_ident = Ident::from_str("allow");
211                     let dc_nested = attr::mk_nested_word_item(Ident::from_str("dead_code"));
212                     let allow_dead_code_item = attr::mk_list_item(DUMMY_SP, allow_ident,
213                                                                   vec![dc_nested]);
214                     let allow_dead_code = attr::mk_attr_outer(DUMMY_SP,
215                                                               attr::mk_attr_id(),
216                                                               allow_dead_code_item);
217
218                     ast::Item {
219                         id,
220                         ident,
221                         attrs: attrs.into_iter()
222                             .filter(|attr| {
223                                 !attr.check_name("main") && !attr.check_name("start")
224                             })
225                             .chain(iter::once(allow_dead_code))
226                             .collect(),
227                         node,
228                         vis,
229                         span,
230                         tokens,
231                     }
232                 }),
233             EntryPointType::None |
234             EntryPointType::OtherMain => folded,
235         };
236
237         SmallVector::one(folded)
238     }
239
240     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
241 }
242
243 fn mk_reexport_mod(cx: &mut TestCtxt,
244                    parent: ast::NodeId,
245                    tests: Vec<Ident>,
246                    tested_submods: Vec<(Ident, Ident)>)
247                    -> (P<ast::Item>, Ident) {
248     let super_ = Ident::from_str("super");
249
250     let items = tests.into_iter().map(|r| {
251         cx.ext_cx.item_use_simple(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
252                                   cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
253     }).chain(tested_submods.into_iter().map(|(r, sym)| {
254         let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
255         cx.ext_cx.item_use_simple_(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
256                                    Some(r), path)
257     })).collect();
258
259     let reexport_mod = ast::Mod {
260         inner: DUMMY_SP,
261         items,
262     };
263
264     let sym = Ident::with_empty_ctxt(Symbol::gensym("__test_reexports"));
265     let parent = if parent == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { parent };
266     cx.ext_cx.current_expansion.mark = cx.ext_cx.resolver.get_module_scope(parent);
267     let it = cx.ext_cx.monotonic_expander().fold_item(P(ast::Item {
268         ident: sym,
269         attrs: Vec::new(),
270         id: ast::DUMMY_NODE_ID,
271         node: ast::ItemKind::Mod(reexport_mod),
272         vis: dummy_spanned(ast::VisibilityKind::Public),
273         span: DUMMY_SP,
274         tokens: None,
275     })).pop().unwrap();
276
277     (it, sym)
278 }
279
280 fn generate_test_harness(sess: &ParseSess,
281                          resolver: &mut dyn Resolver,
282                          reexport_test_harness_main: Option<Symbol>,
283                          krate: ast::Crate,
284                          sd: &errors::Handler,
285                          features: &Features) -> ast::Crate {
286     // Remove the entry points
287     let mut cleaner = EntryPointCleaner { depth: 0 };
288     let krate = cleaner.fold_crate(krate);
289
290     let mark = Mark::fresh(Mark::root());
291
292     let mut econfig = ExpansionConfig::default("test".to_string());
293     econfig.features = Some(features);
294
295     let cx = TestCtxt {
296         span_diagnostic: sd,
297         ext_cx: ExtCtxt::new(sess, econfig, resolver),
298         path: Vec::new(),
299         testfns: Vec::new(),
300         reexport_test_harness_main,
301         // NB: doesn't consider the value of `--crate-name` passed on the command line.
302         is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false),
303         toplevel_reexport: None,
304         ctxt: SyntaxContext::empty().apply_mark(mark),
305         features,
306     };
307
308     mark.set_expn_info(ExpnInfo {
309         call_site: DUMMY_SP,
310         def_site: None,
311         format: MacroAttribute(Symbol::intern("test")),
312         allow_internal_unstable: true,
313         allow_internal_unsafe: false,
314         local_inner_macros: false,
315         edition: hygiene::default_edition(),
316     });
317
318     TestHarnessGenerator {
319         cx,
320         tests: Vec::new(),
321         tested_submods: Vec::new(),
322     }.fold_crate(krate)
323 }
324
325 /// Craft a span that will be ignored by the stability lint's
326 /// call to codemap's `is_internal` check.
327 /// The expanded code calls some unstable functions in the test crate.
328 fn ignored_span(cx: &TestCtxt, sp: Span) -> Span {
329     sp.with_ctxt(cx.ctxt)
330 }
331
332 enum HasTestSignature {
333     Yes,
334     No(BadTestSignature),
335 }
336
337 #[derive(PartialEq)]
338 enum BadTestSignature {
339     NotEvenAFunction,
340     WrongTypeSignature,
341     NoArgumentsAllowed,
342     ShouldPanicOnlyWithNoArgs,
343 }
344
345 fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
346     let has_test_attr = attr::contains_name(&i.attrs, "test");
347
348     fn has_test_signature(_cx: &TestCtxt, i: &ast::Item) -> HasTestSignature {
349         let has_should_panic_attr = attr::contains_name(&i.attrs, "should_panic");
350         match i.node {
351             ast::ItemKind::Fn(ref decl, _, ref generics, _) => {
352                 // If the termination trait is active, the compiler will check that the output
353                 // type implements the `Termination` trait as `libtest` enforces that.
354                 let has_output = match decl.output {
355                     ast::FunctionRetTy::Default(..) => false,
356                     ast::FunctionRetTy::Ty(ref t) if t.node.is_empty_tuple() => false,
357                     _ => true
358                 };
359
360                 if !decl.inputs.is_empty() {
361                     return No(BadTestSignature::NoArgumentsAllowed);
362                 }
363
364                 match (has_output, has_should_panic_attr) {
365                     (true, true) => No(BadTestSignature::ShouldPanicOnlyWithNoArgs),
366                     (true, false) => if !generics.params.is_empty() {
367                         No(BadTestSignature::WrongTypeSignature)
368                     } else {
369                         Yes
370                     },
371                     (false, _) => Yes
372                 }
373             }
374             _ => No(BadTestSignature::NotEvenAFunction),
375         }
376     }
377
378     let has_test_signature = if has_test_attr {
379         let diag = cx.span_diagnostic;
380         match has_test_signature(cx, i) {
381             Yes => true,
382             No(cause) => {
383                 match cause {
384                     BadTestSignature::NotEvenAFunction =>
385                         diag.span_err(i.span, "only functions may be used as tests"),
386                     BadTestSignature::WrongTypeSignature =>
387                         diag.span_err(i.span,
388                                       "functions used as tests must have signature fn() -> ()"),
389                     BadTestSignature::NoArgumentsAllowed =>
390                         diag.span_err(i.span, "functions used as tests can not have any arguments"),
391                     BadTestSignature::ShouldPanicOnlyWithNoArgs =>
392                         diag.span_err(i.span, "functions using `#[should_panic]` must return `()`"),
393                 }
394                 false
395             }
396         }
397     } else {
398         false
399     };
400
401     has_test_attr && has_test_signature
402 }
403
404 fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
405     let has_bench_attr = attr::contains_name(&i.attrs, "bench");
406
407     fn has_bench_signature(_cx: &TestCtxt, i: &ast::Item) -> bool {
408         match i.node {
409             ast::ItemKind::Fn(ref decl, _, _, _) => {
410                 // NB: inadequate check, but we're running
411                 // well before resolve, can't get too deep.
412                 decl.inputs.len() == 1
413             }
414             _ => false
415         }
416     }
417
418     let has_bench_signature = has_bench_signature(cx, i);
419
420     if has_bench_attr && !has_bench_signature {
421         let diag = cx.span_diagnostic;
422
423         diag.span_err(i.span, "functions used as benches must have signature \
424                                    `fn(&mut Bencher) -> impl Termination`");
425     }
426
427     has_bench_attr && has_bench_signature
428 }
429
430 fn is_ignored(i: &ast::Item) -> bool {
431     attr::contains_name(&i.attrs, "ignore")
432 }
433
434 fn is_allowed_fail(i: &ast::Item) -> bool {
435     attr::contains_name(&i.attrs, "allow_fail")
436 }
437
438 fn should_panic(i: &ast::Item, cx: &TestCtxt) -> ShouldPanic {
439     match attr::find_by_name(&i.attrs, "should_panic") {
440         Some(attr) => {
441             let sd = cx.span_diagnostic;
442             if attr.is_value_str() {
443                 sd.struct_span_warn(
444                     attr.span(),
445                     "attribute must be of the form: \
446                      `#[should_panic]` or \
447                      `#[should_panic(expected = \"error message\")]`"
448                 ).note("Errors in this attribute were erroneously allowed \
449                         and will become a hard error in a future release.")
450                 .emit();
451                 return ShouldPanic::Yes(None);
452             }
453             match attr.meta_item_list() {
454                 // Handle #[should_panic]
455                 None => ShouldPanic::Yes(None),
456                 // Handle #[should_panic(expected = "foo")]
457                 Some(list) => {
458                     let msg = list.iter()
459                         .find(|mi| mi.check_name("expected"))
460                         .and_then(|mi| mi.meta_item())
461                         .and_then(|mi| mi.value_str());
462                     if list.len() != 1 || msg.is_none() {
463                         sd.struct_span_warn(
464                             attr.span(),
465                             "argument must be of the form: \
466                              `expected = \"error message\"`"
467                         ).note("Errors in this attribute were erroneously \
468                                 allowed and will become a hard error in a \
469                                 future release.").emit();
470                         ShouldPanic::Yes(None)
471                     } else {
472                         ShouldPanic::Yes(msg)
473                     }
474                 },
475             }
476         }
477         None => ShouldPanic::No,
478     }
479 }
480
481 /*
482
483 We're going to be building a module that looks more or less like:
484
485 mod __test {
486   extern crate test (name = "test", vers = "...");
487   fn main() {
488     test::test_main_static(&::os::args()[], tests, test::Options::new())
489   }
490
491   static tests : &'static [test::TestDescAndFn] = &[
492     ... the list of tests in the crate ...
493   ];
494 }
495
496 */
497
498 fn mk_std(cx: &TestCtxt) -> P<ast::Item> {
499     let id_test = Ident::from_str("test");
500     let sp = ignored_span(cx, DUMMY_SP);
501     let (vi, vis, ident) = if cx.is_libtest {
502         (ast::ItemKind::Use(P(ast::UseTree {
503             span: DUMMY_SP,
504             prefix: path_node(vec![id_test]),
505             kind: ast::UseTreeKind::Simple(None, ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID),
506         })),
507          ast::VisibilityKind::Public, keywords::Invalid.ident())
508     } else {
509         (ast::ItemKind::ExternCrate(None), ast::VisibilityKind::Inherited, id_test)
510     };
511     P(ast::Item {
512         id: ast::DUMMY_NODE_ID,
513         ident,
514         node: vi,
515         attrs: vec![],
516         vis: dummy_spanned(vis),
517         span: sp,
518         tokens: None,
519     })
520 }
521
522 fn mk_main(cx: &mut TestCtxt) -> P<ast::Item> {
523     // Writing this out by hand with 'ignored_span':
524     //        pub fn main() {
525     //            #![main]
526     //            use std::slice::AsSlice;
527     //            test::test_main_static(::std::os::args().as_slice(), TESTS, test::Options::new());
528     //        }
529
530     let sp = ignored_span(cx, DUMMY_SP);
531     let ecx = &cx.ext_cx;
532
533     // test::test_main_static
534     let test_main_path =
535         ecx.path(sp, vec![Ident::from_str("test"), Ident::from_str("test_main_static")]);
536
537     // test::test_main_static(...)
538     let test_main_path_expr = ecx.expr_path(test_main_path);
539     let tests_ident_expr = ecx.expr_ident(sp, Ident::from_str("TESTS"));
540     let call_test_main = ecx.expr_call(sp, test_main_path_expr,
541                                        vec![tests_ident_expr]);
542     let call_test_main = ecx.stmt_expr(call_test_main);
543     // #![main]
544     let main_meta = ecx.meta_word(sp, Symbol::intern("main"));
545     let main_attr = ecx.attribute(sp, main_meta);
546     // pub fn main() { ... }
547     let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
548     let main_body = ecx.block(sp, vec![call_test_main]);
549     let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], ast::FunctionRetTy::Ty(main_ret_ty)),
550                            ast::FnHeader::default(),
551                            ast::Generics::default(),
552                            main_body);
553     P(ast::Item {
554         ident: Ident::from_str("main"),
555         attrs: vec![main_attr],
556         id: ast::DUMMY_NODE_ID,
557         node: main,
558         vis: dummy_spanned(ast::VisibilityKind::Public),
559         span: sp,
560         tokens: None,
561     })
562 }
563
564 fn mk_test_module(cx: &mut TestCtxt) -> (P<ast::Item>, Option<P<ast::Item>>) {
565     // Link to test crate
566     let import = mk_std(cx);
567
568     // A constant vector of test descriptors.
569     let tests = mk_tests(cx);
570
571     // The synthesized main function which will call the console test runner
572     // with our list of tests
573     let mainfn = mk_main(cx);
574
575     let testmod = ast::Mod {
576         inner: DUMMY_SP,
577         items: vec![import, mainfn, tests],
578     };
579     let item_ = ast::ItemKind::Mod(testmod);
580     let mod_ident = Ident::with_empty_ctxt(Symbol::gensym("__test"));
581
582     let mut expander = cx.ext_cx.monotonic_expander();
583     let item = expander.fold_item(P(ast::Item {
584         id: ast::DUMMY_NODE_ID,
585         ident: mod_ident,
586         attrs: vec![],
587         node: item_,
588         vis: dummy_spanned(ast::VisibilityKind::Public),
589         span: DUMMY_SP,
590         tokens: None,
591     })).pop().unwrap();
592     let reexport = cx.reexport_test_harness_main.map(|s| {
593         // building `use __test::main as <ident>;`
594         let rename = Ident::with_empty_ctxt(s);
595
596         let use_path = ast::UseTree {
597             span: DUMMY_SP,
598             prefix: path_node(vec![mod_ident, Ident::from_str("main")]),
599             kind: ast::UseTreeKind::Simple(Some(rename), ast::DUMMY_NODE_ID, ast::DUMMY_NODE_ID),
600         };
601
602         expander.fold_item(P(ast::Item {
603             id: ast::DUMMY_NODE_ID,
604             ident: keywords::Invalid.ident(),
605             attrs: vec![],
606             node: ast::ItemKind::Use(P(use_path)),
607             vis: dummy_spanned(ast::VisibilityKind::Inherited),
608             span: DUMMY_SP,
609             tokens: None,
610         })).pop().unwrap()
611     });
612
613     debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item));
614
615     (item, reexport)
616 }
617
618 fn nospan<T>(t: T) -> codemap::Spanned<T> {
619     codemap::Spanned { node: t, span: DUMMY_SP }
620 }
621
622 fn path_node(ids: Vec<Ident>) -> ast::Path {
623     ast::Path {
624         span: DUMMY_SP,
625         segments: ids.into_iter().map(|id| ast::PathSegment::from_ident(id)).collect(),
626     }
627 }
628
629 fn path_name_i(idents: &[Ident]) -> String {
630     let mut path_name = "".to_string();
631     let mut idents_iter = idents.iter().peekable();
632     while let Some(ident) = idents_iter.next() {
633         path_name.push_str(&ident.as_str());
634         if let Some(_) = idents_iter.peek() {
635             path_name.push_str("::")
636         }
637     }
638     path_name
639 }
640
641 fn mk_tests(cx: &TestCtxt) -> P<ast::Item> {
642     // The vector of test_descs for this crate
643     let test_descs = mk_test_descs(cx);
644
645     // FIXME #15962: should be using quote_item, but that stringifies
646     // __test_reexports, causing it to be reinterned, losing the
647     // gensym information.
648     let sp = ignored_span(cx, DUMMY_SP);
649     let ecx = &cx.ext_cx;
650     let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"),
651                                                     ecx.ident_of("test"),
652                                                     ecx.ident_of("TestDescAndFn")]));
653     let static_lt = ecx.lifetime(sp, keywords::StaticLifetime.ident());
654     // &'static [self::test::TestDescAndFn]
655     let static_type = ecx.ty_rptr(sp,
656                                   ecx.ty(sp, ast::TyKind::Slice(struct_type)),
657                                   Some(static_lt),
658                                   ast::Mutability::Immutable);
659     // static TESTS: $static_type = &[...];
660     ecx.item_const(sp,
661                    ecx.ident_of("TESTS"),
662                    static_type,
663                    test_descs)
664 }
665
666 fn mk_test_descs(cx: &TestCtxt) -> P<ast::Expr> {
667     debug!("building test vector from {} tests", cx.testfns.len());
668
669     P(ast::Expr {
670         id: ast::DUMMY_NODE_ID,
671         node: ast::ExprKind::AddrOf(ast::Mutability::Immutable,
672             P(ast::Expr {
673                 id: ast::DUMMY_NODE_ID,
674                 node: ast::ExprKind::Array(cx.testfns.iter().map(|test| {
675                     mk_test_desc_and_fn_rec(cx, test)
676                 }).collect()),
677                 span: DUMMY_SP,
678                 attrs: ast::ThinVec::new(),
679             })),
680         span: DUMMY_SP,
681         attrs: ast::ThinVec::new(),
682     })
683 }
684
685 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> {
686     // FIXME #15962: should be using quote_expr, but that stringifies
687     // __test_reexports, causing it to be reinterned, losing the
688     // gensym information.
689
690     let span = ignored_span(cx, test.span);
691     let ecx = &cx.ext_cx;
692     let self_id = ecx.ident_of("self");
693     let test_id = ecx.ident_of("test");
694
695     // creates self::test::$name
696     let test_path = |name| {
697         ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)])
698     };
699     // creates $name: $expr
700     let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr);
701
702     // path to the #[test] function: "foo::bar::baz"
703     let path_string = path_name_i(&test.path[..]);
704
705     debug!("encoding {}", path_string);
706
707     let name_expr = ecx.expr_str(span, Symbol::intern(&path_string));
708
709     // self::test::StaticTestName($name_expr)
710     let name_expr = ecx.expr_call(span,
711                                   ecx.expr_path(test_path("StaticTestName")),
712                                   vec![name_expr]);
713
714     let ignore_expr = ecx.expr_bool(span, test.ignore);
715     let should_panic_path = |name| {
716         ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)])
717     };
718     let fail_expr = match test.should_panic {
719         ShouldPanic::No => ecx.expr_path(should_panic_path("No")),
720         ShouldPanic::Yes(msg) => {
721             match msg {
722                 Some(msg) => {
723                     let msg = ecx.expr_str(span, msg);
724                     let path = should_panic_path("YesWithMessage");
725                     ecx.expr_call(span, ecx.expr_path(path), vec![msg])
726                 }
727                 None => ecx.expr_path(should_panic_path("Yes")),
728             }
729         }
730     };
731     let allow_fail_expr = ecx.expr_bool(span, test.allow_fail);
732
733     // self::test::TestDesc { ... }
734     let desc_expr = ecx.expr_struct(
735         span,
736         test_path("TestDesc"),
737         vec![field("name", name_expr),
738              field("ignore", ignore_expr),
739              field("should_panic", fail_expr),
740              field("allow_fail", allow_fail_expr)]);
741
742     let mut visible_path = vec![];
743     if cx.features.extern_absolute_paths {
744         visible_path.push(keywords::Crate.ident());
745     }
746     match cx.toplevel_reexport {
747         Some(id) => visible_path.push(id),
748         None => {
749             let diag = cx.span_diagnostic;
750             diag.bug("expected to find top-level re-export name, but found None");
751         }
752     };
753     visible_path.extend_from_slice(&test.path[..]);
754
755     // Rather than directly give the test function to the test
756     // harness, we create a wrapper like one of the following:
757     //
758     //     || test::assert_test_result(real_function()) // for test
759     //     |b| test::assert_test_result(real_function(b)) // for bench
760     //
761     // this will coerce into a fn pointer that is specialized to the
762     // actual return type of `real_function` (Typically `()`, but not always).
763     let fn_expr = {
764         // construct `real_function()` (this will be inserted into the overall expr)
765         let real_function_expr = ecx.expr_path(ecx.path_global(span, visible_path));
766         // construct path `test::assert_test_result`
767         let assert_test_result = test_path("assert_test_result");
768         if test.bench {
769             // construct `|b| {..}`
770             let b_ident = Ident::with_empty_ctxt(Symbol::gensym("b"));
771             let b_expr = ecx.expr_ident(span, b_ident);
772             ecx.lambda(
773                 span,
774                 vec![b_ident],
775                 // construct `assert_test_result(..)`
776                 ecx.expr_call(
777                     span,
778                     ecx.expr_path(assert_test_result),
779                     vec![
780                         // construct `real_function(b)`
781                         ecx.expr_call(
782                             span,
783                             real_function_expr,
784                             vec![b_expr],
785                         )
786                     ],
787                 ),
788             )
789         } else {
790             // construct `|| {..}`
791             ecx.lambda(
792                 span,
793                 vec![],
794                 // construct `assert_test_result(..)`
795                 ecx.expr_call(
796                     span,
797                     ecx.expr_path(assert_test_result),
798                     vec![
799                         // construct `real_function()`
800                         ecx.expr_call(
801                             span,
802                             real_function_expr,
803                             vec![],
804                         )
805                     ],
806                 ),
807             )
808         }
809     };
810
811     let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" };
812
813     // self::test::$variant_name($fn_expr)
814     let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]);
815
816     // self::test::TestDescAndFn { ... }
817     ecx.expr_struct(span,
818                     test_path("TestDescAndFn"),
819                     vec![field("desc", desc_expr),
820                          field("testfn", testfn_expr)])
821 }