]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/test.rs
Use `Ident` instead of `Name` in `MetaItem`
[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, NameAndSpan, 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::{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 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(_, ast::Unsafety::Unsafe, _, _, _, _) => {
128                     let diag = self.cx.span_diagnostic;
129                     diag.span_fatal(i.span, "unsafe functions cannot be used for tests").raise();
130                 }
131                 _ => {
132                     debug!("this is a test function");
133                     let test = Test {
134                         span: i.span,
135                         path: self.cx.path.clone(),
136                         bench: is_bench_fn(&self.cx, &i),
137                         ignore: is_ignored(&i),
138                         should_panic: should_panic(&i, &self.cx),
139                         allow_fail: is_allowed_fail(&i),
140                     };
141                     self.cx.testfns.push(test);
142                     self.tests.push(i.ident);
143                 }
144             }
145         }
146
147         let mut item = i.into_inner();
148         // We don't want to recurse into anything other than mods, since
149         // mods or tests inside of functions will break things
150         if let ast::ItemKind::Mod(module) = item.node {
151             let tests = mem::replace(&mut self.tests, Vec::new());
152             let tested_submods = mem::replace(&mut self.tested_submods, Vec::new());
153             let mut mod_folded = fold::noop_fold_mod(module, self);
154             let tests = mem::replace(&mut self.tests, tests);
155             let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
156
157             if !tests.is_empty() || !tested_submods.is_empty() {
158                 let (it, sym) = mk_reexport_mod(&mut self.cx, item.id, tests, tested_submods);
159                 mod_folded.items.push(it);
160
161                 if !self.cx.path.is_empty() {
162                     self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym));
163                 } else {
164                     debug!("pushing nothing, sym: {:?}", sym);
165                     self.cx.toplevel_reexport = Some(sym);
166                 }
167             }
168             item.node = ast::ItemKind::Mod(mod_folded);
169         }
170         if ident.name != keywords::Invalid.name() {
171             self.cx.path.pop();
172         }
173         SmallVector::one(P(item))
174     }
175
176     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
177 }
178
179 struct EntryPointCleaner {
180     // Current depth in the ast
181     depth: usize,
182 }
183
184 impl fold::Folder for EntryPointCleaner {
185     fn fold_item(&mut self, i: P<ast::Item>) -> SmallVector<P<ast::Item>> {
186         self.depth += 1;
187         let folded = fold::noop_fold_item(i, self).expect_one("noop did something");
188         self.depth -= 1;
189
190         // Remove any #[main] or #[start] from the AST so it doesn't
191         // clash with the one we're going to add, but mark it as
192         // #[allow(dead_code)] to avoid printing warnings.
193         let folded = match entry::entry_point_type(&folded, self.depth) {
194             EntryPointType::MainNamed |
195             EntryPointType::MainAttr |
196             EntryPointType::Start =>
197                 folded.map(|ast::Item {id, ident, attrs, node, vis, span, tokens}| {
198                     let allow_ident = Ident::from_str("allow");
199                     let dc_nested = attr::mk_nested_word_item(Ident::from_str("dead_code"));
200                     let allow_dead_code_item = attr::mk_list_item(DUMMY_SP, allow_ident,
201                                                                   vec![dc_nested]);
202                     let allow_dead_code = attr::mk_attr_outer(DUMMY_SP,
203                                                               attr::mk_attr_id(),
204                                                               allow_dead_code_item);
205
206                     ast::Item {
207                         id,
208                         ident,
209                         attrs: attrs.into_iter()
210                             .filter(|attr| {
211                                 !attr.check_name("main") && !attr.check_name("start")
212                             })
213                             .chain(iter::once(allow_dead_code))
214                             .collect(),
215                         node,
216                         vis,
217                         span,
218                         tokens,
219                     }
220                 }),
221             EntryPointType::None |
222             EntryPointType::OtherMain => folded,
223         };
224
225         SmallVector::one(folded)
226     }
227
228     fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { mac }
229 }
230
231 fn mk_reexport_mod(cx: &mut TestCtxt,
232                    parent: ast::NodeId,
233                    tests: Vec<Ident>,
234                    tested_submods: Vec<(Ident, Ident)>)
235                    -> (P<ast::Item>, Ident) {
236     let super_ = Ident::from_str("super");
237
238     let items = tests.into_iter().map(|r| {
239         cx.ext_cx.item_use_simple(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
240                                   cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
241     }).chain(tested_submods.into_iter().map(|(r, sym)| {
242         let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
243         cx.ext_cx.item_use_simple_(DUMMY_SP, dummy_spanned(ast::VisibilityKind::Public),
244                                    Some(r), path)
245     })).collect();
246
247     let reexport_mod = ast::Mod {
248         inner: DUMMY_SP,
249         items,
250     };
251
252     let sym = Ident::with_empty_ctxt(Symbol::gensym("__test_reexports"));
253     let parent = if parent == ast::DUMMY_NODE_ID { ast::CRATE_NODE_ID } else { parent };
254     cx.ext_cx.current_expansion.mark = cx.ext_cx.resolver.get_module_scope(parent);
255     let it = cx.ext_cx.monotonic_expander().fold_item(P(ast::Item {
256         ident: sym,
257         attrs: Vec::new(),
258         id: ast::DUMMY_NODE_ID,
259         node: ast::ItemKind::Mod(reexport_mod),
260         vis: dummy_spanned(ast::VisibilityKind::Public),
261         span: DUMMY_SP,
262         tokens: None,
263     })).pop().unwrap();
264
265     (it, sym)
266 }
267
268 fn generate_test_harness(sess: &ParseSess,
269                          resolver: &mut Resolver,
270                          reexport_test_harness_main: Option<Symbol>,
271                          krate: ast::Crate,
272                          sd: &errors::Handler,
273                          features: &Features) -> ast::Crate {
274     // Remove the entry points
275     let mut cleaner = EntryPointCleaner { depth: 0 };
276     let krate = cleaner.fold_crate(krate);
277
278     let mark = Mark::fresh(Mark::root());
279
280     let mut econfig = ExpansionConfig::default("test".to_string());
281     econfig.features = Some(features);
282
283     let cx = TestCtxt {
284         span_diagnostic: sd,
285         ext_cx: ExtCtxt::new(sess, econfig, resolver),
286         path: Vec::new(),
287         testfns: Vec::new(),
288         reexport_test_harness_main,
289         // NB: doesn't consider the value of `--crate-name` passed on the command line.
290         is_libtest: attr::find_crate_name(&krate.attrs).map(|s| s == "test").unwrap_or(false),
291         toplevel_reexport: None,
292         ctxt: SyntaxContext::empty().apply_mark(mark),
293         features,
294     };
295
296     mark.set_expn_info(ExpnInfo {
297         call_site: DUMMY_SP,
298         callee: NameAndSpan {
299             format: MacroAttribute(Symbol::intern("test")),
300             span: None,
301             allow_internal_unstable: true,
302             allow_internal_unsafe: false,
303         }
304     });
305
306     TestHarnessGenerator {
307         cx,
308         tests: Vec::new(),
309         tested_submods: Vec::new(),
310     }.fold_crate(krate)
311 }
312
313 /// Craft a span that will be ignored by the stability lint's
314 /// call to codemap's `is_internal` check.
315 /// The expanded code calls some unstable functions in the test crate.
316 fn ignored_span(cx: &TestCtxt, sp: Span) -> Span {
317     sp.with_ctxt(cx.ctxt)
318 }
319
320 #[derive(PartialEq)]
321 enum HasTestSignature {
322     Yes,
323     No,
324     NotEvenAFunction,
325 }
326
327 fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
328     let has_test_attr = attr::contains_name(&i.attrs, "test");
329
330     fn has_test_signature(cx: &TestCtxt, i: &ast::Item) -> HasTestSignature {
331         match i.node {
332             ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
333                 // If the termination trait is active, the compiler will check that the output
334                 // type implements the `Termination` trait as `libtest` enforces that.
335                 let output_matches = if cx.features.termination_trait_test {
336                     true
337                 } else {
338                     let no_output = match decl.output {
339                         ast::FunctionRetTy::Default(..) => true,
340                         ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
341                         _ => false
342                     };
343
344                     no_output && !generics.is_parameterized()
345                 };
346
347                 if decl.inputs.is_empty() && output_matches {
348                     Yes
349                 } else {
350                     No
351                 }
352             }
353             _ => NotEvenAFunction,
354         }
355     }
356
357     let has_test_signature = if has_test_attr {
358         let diag = cx.span_diagnostic;
359         match has_test_signature(cx, i) {
360             Yes => true,
361             No => {
362                 if cx.features.termination_trait_test {
363                     diag.span_err(i.span, "functions used as tests can not have any arguments");
364                 } else {
365                     diag.span_err(i.span, "functions used as tests must have signature fn() -> ()");
366                 }
367                 false
368             },
369             NotEvenAFunction => {
370                 diag.span_err(i.span, "only functions may be used as tests");
371                 false
372             },
373         }
374     } else {
375         false
376     };
377
378     has_test_attr && has_test_signature
379 }
380
381 fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
382     let has_bench_attr = attr::contains_name(&i.attrs, "bench");
383
384     fn has_bench_signature(cx: &TestCtxt, i: &ast::Item) -> bool {
385         match i.node {
386             ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
387                 let input_cnt = decl.inputs.len();
388
389                 // If the termination trait is active, the compiler will check that the output
390                 // type implements the `Termination` trait as `libtest` enforces that.
391                 let output_matches = if cx.features.termination_trait_test {
392                     true
393                 } else {
394                     let no_output = match decl.output {
395                         ast::FunctionRetTy::Default(..) => true,
396                         ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
397                         _ => false
398                     };
399                     let tparm_cnt = generics.params.iter()
400                         .filter(|param| param.is_type_param())
401                         .count();
402
403                     no_output && tparm_cnt == 0
404                 };
405
406                 // NB: inadequate check, but we're running
407                 // well before resolve, can't get too deep.
408                 input_cnt == 1 && output_matches
409             }
410           _ => false
411         }
412     }
413
414     let has_bench_signature = has_bench_signature(cx, i);
415
416     if has_bench_attr && !has_bench_signature {
417         let diag = cx.span_diagnostic;
418
419         if cx.features.termination_trait_test {
420             diag.span_err(i.span, "functions used as benches must have signature \
421                                    `fn(&mut Bencher) -> impl Termination`");
422         } else {
423             diag.span_err(i.span, "functions used as benches must have signature \
424                                    `fn(&mut Bencher) -> ()`");
425         }
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),
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::Unsafety::Normal,
552                            dummy_spanned(ast::Constness::NotConst),
553                            ::abi::Abi::Rust, ast::Generics::default(), 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)),
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.name.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 }