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