]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/test.rs
Auto merge of #52046 - cramertj:fix-generator-mir, r=eddyb
[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 #[derive(PartialEq)]
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 == ast::TyKind::Tup(vec![]) => 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 let Some(_) = idents_iter.peek() {
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: ast::ThinVec::new(),
680             })),
681         span: DUMMY_SP,
682         attrs: ast::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 }