]> git.proxmox.com Git - rustc.git/blob - src/libsyntax/test.rs
Imported Upstream version 1.9.0+dfsg1
[rustc.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 use self::HasTestSignature::*;
16
17 use std::iter;
18 use std::slice;
19 use std::mem;
20 use std::vec;
21 use attr::AttrMetaMethods;
22 use attr;
23 use codemap::{DUMMY_SP, Span, ExpnInfo, NameAndSpan, MacroAttribute};
24 use codemap;
25 use errors;
26 use config;
27 use entry::{self, EntryPointType};
28 use ext::base::ExtCtxt;
29 use ext::build::AstBuilder;
30 use ext::expand::ExpansionConfig;
31 use fold::Folder;
32 use util::move_map::MoveMap;
33 use fold;
34 use parse::token::{intern, InternedString};
35 use parse::{token, ParseSess};
36 use print::pprust;
37 use ast;
38 use ptr::P;
39 use util::small_vector::SmallVector;
40
41 enum ShouldPanic {
42 No,
43 Yes(Option<InternedString>),
44 }
45
46 struct Test {
47 span: Span,
48 path: Vec<ast::Ident> ,
49 bench: bool,
50 ignore: bool,
51 should_panic: ShouldPanic
52 }
53
54 struct TestCtxt<'a> {
55 sess: &'a ParseSess,
56 span_diagnostic: &'a errors::Handler,
57 path: Vec<ast::Ident>,
58 ext_cx: ExtCtxt<'a>,
59 testfns: Vec<Test>,
60 reexport_test_harness_main: Option<InternedString>,
61 is_test_crate: bool,
62 config: ast::CrateConfig,
63
64 // top-level re-export submodule, filled out after folding is finished
65 toplevel_reexport: Option<ast::Ident>,
66 }
67
68 // Traverse the crate, collecting all the test functions, eliding any
69 // existing main functions, and synthesizing a main test harness
70 pub fn modify_for_testing(sess: &ParseSess,
71 cfg: &ast::CrateConfig,
72 krate: ast::Crate,
73 span_diagnostic: &errors::Handler) -> ast::Crate {
74 // We generate the test harness when building in the 'test'
75 // configuration, either with the '--test' or '--cfg test'
76 // command line options.
77 let should_test = attr::contains_name(&krate.config, "test");
78
79 // Check for #[reexport_test_harness_main = "some_name"] which
80 // creates a `use some_name = __test::main;`. This needs to be
81 // unconditional, so that the attribute is still marked as used in
82 // non-test builds.
83 let reexport_test_harness_main =
84 attr::first_attr_value_str_by_name(&krate.attrs,
85 "reexport_test_harness_main");
86
87 if should_test {
88 generate_test_harness(sess, reexport_test_harness_main, krate, cfg, span_diagnostic)
89 } else {
90 strip_test_functions(span_diagnostic, krate)
91 }
92 }
93
94 struct TestHarnessGenerator<'a> {
95 cx: TestCtxt<'a>,
96 tests: Vec<ast::Ident>,
97
98 // submodule name, gensym'd identifier for re-exports
99 tested_submods: Vec<(ast::Ident, ast::Ident)>,
100 }
101
102 impl<'a> fold::Folder for TestHarnessGenerator<'a> {
103 fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate {
104 let mut folded = fold::noop_fold_crate(c, self);
105
106 // Add a special __test module to the crate that will contain code
107 // generated for the test harness
108 let (mod_, reexport) = mk_test_module(&mut self.cx);
109 match reexport {
110 Some(re) => folded.module.items.push(re),
111 None => {}
112 }
113 folded.module.items.push(mod_);
114 folded
115 }
116
117 fn fold_item(&mut self, i: P<ast::Item>) -> SmallVector<P<ast::Item>> {
118 let ident = i.ident;
119 if ident.name != token::special_idents::invalid.name {
120 self.cx.path.push(ident);
121 }
122 debug!("current path: {}", path_name_i(&self.cx.path));
123
124 let i = if is_test_fn(&self.cx, &i) || is_bench_fn(&self.cx, &i) {
125 match i.node {
126 ast::ItemKind::Fn(_, ast::Unsafety::Unsafe, _, _, _, _) => {
127 let diag = self.cx.span_diagnostic;
128 panic!(diag.span_fatal(i.span, "unsafe functions cannot be used for tests"));
129 }
130 _ => {
131 debug!("this is a test function");
132 let test = Test {
133 span: i.span,
134 path: self.cx.path.clone(),
135 bench: is_bench_fn(&self.cx, &i),
136 ignore: is_ignored(&i),
137 should_panic: should_panic(&i)
138 };
139 self.cx.testfns.push(test);
140 self.tests.push(i.ident);
141 // debug!("have {} test/bench functions",
142 // cx.testfns.len());
143
144 // Make all tests public so we can call them from outside
145 // the module (note that the tests are re-exported and must
146 // be made public themselves to avoid privacy errors).
147 i.map(|mut i| {
148 i.vis = ast::Visibility::Public;
149 i
150 })
151 }
152 }
153 } else {
154 i
155 };
156
157 // We don't want to recurse into anything other than mods, since
158 // mods or tests inside of functions will break things
159 let res = match i.node {
160 ast::ItemKind::Mod(..) => fold::noop_fold_item(i, self),
161 _ => SmallVector::one(i),
162 };
163 if ident.name != token::special_idents::invalid.name {
164 self.cx.path.pop();
165 }
166 res
167 }
168
169 fn fold_mod(&mut self, m: ast::Mod) -> ast::Mod {
170 let tests = mem::replace(&mut self.tests, Vec::new());
171 let tested_submods = mem::replace(&mut self.tested_submods, Vec::new());
172 let mut mod_folded = fold::noop_fold_mod(m, self);
173 let tests = mem::replace(&mut self.tests, tests);
174 let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
175
176 if !tests.is_empty() || !tested_submods.is_empty() {
177 let (it, sym) = mk_reexport_mod(&mut self.cx, tests, tested_submods);
178 mod_folded.items.push(it);
179
180 if !self.cx.path.is_empty() {
181 self.tested_submods.push((self.cx.path[self.cx.path.len()-1], sym));
182 } else {
183 debug!("pushing nothing, sym: {:?}", sym);
184 self.cx.toplevel_reexport = Some(sym);
185 }
186 }
187
188 mod_folded
189 }
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>) -> SmallVector<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}| {
211 let allow_str = InternedString::new("allow");
212 let dead_code_str = InternedString::new("dead_code");
213 let allow_dead_code_item =
214 attr::mk_list_item(allow_str,
215 vec![attr::mk_word_item(dead_code_str)]);
216 let allow_dead_code = attr::mk_attr_outer(attr::mk_attr_id(),
217 allow_dead_code_item);
218
219 ast::Item {
220 id: id,
221 ident: 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: node,
229 vis: vis,
230 span: span
231 }
232 }),
233 EntryPointType::None |
234 EntryPointType::OtherMain => folded,
235 };
236
237 SmallVector::one(folded)
238 }
239 }
240
241 fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec<ast::Ident>,
242 tested_submods: Vec<(ast::Ident, ast::Ident)>) -> (P<ast::Item>, ast::Ident) {
243 let super_ = token::str_to_ident("super");
244
245 let items = tests.into_iter().map(|r| {
246 cx.ext_cx.item_use_simple(DUMMY_SP, ast::Visibility::Public,
247 cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
248 }).chain(tested_submods.into_iter().map(|(r, sym)| {
249 let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
250 cx.ext_cx.item_use_simple_(DUMMY_SP, ast::Visibility::Public, r, path)
251 }));
252
253 let reexport_mod = ast::Mod {
254 inner: DUMMY_SP,
255 items: items.collect(),
256 };
257
258 let sym = token::gensym_ident("__test_reexports");
259 let it = P(ast::Item {
260 ident: sym.clone(),
261 attrs: Vec::new(),
262 id: ast::DUMMY_NODE_ID,
263 node: ast::ItemKind::Mod(reexport_mod),
264 vis: ast::Visibility::Public,
265 span: DUMMY_SP,
266 });
267
268 (it, sym)
269 }
270
271 fn generate_test_harness(sess: &ParseSess,
272 reexport_test_harness_main: Option<InternedString>,
273 krate: ast::Crate,
274 cfg: &ast::CrateConfig,
275 sd: &errors::Handler) -> ast::Crate {
276 // Remove the entry points
277 let mut cleaner = EntryPointCleaner { depth: 0 };
278 let krate = cleaner.fold_crate(krate);
279
280 let mut feature_gated_cfgs = vec![];
281 let mut cx: TestCtxt = TestCtxt {
282 sess: sess,
283 span_diagnostic: sd,
284 ext_cx: ExtCtxt::new(sess, cfg.clone(),
285 ExpansionConfig::default("test".to_string()),
286 &mut feature_gated_cfgs),
287 path: Vec::new(),
288 testfns: Vec::new(),
289 reexport_test_harness_main: reexport_test_harness_main,
290 is_test_crate: is_test_crate(&krate),
291 config: krate.config.clone(),
292 toplevel_reexport: None,
293 };
294 cx.ext_cx.crate_root = Some("std");
295
296 cx.ext_cx.bt_push(ExpnInfo {
297 call_site: DUMMY_SP,
298 callee: NameAndSpan {
299 format: MacroAttribute(intern("test")),
300 span: None,
301 allow_internal_unstable: false,
302 }
303 });
304
305 let mut fold = TestHarnessGenerator {
306 cx: cx,
307 tests: Vec::new(),
308 tested_submods: Vec::new(),
309 };
310 let res = fold.fold_crate(krate);
311 fold.cx.ext_cx.bt_pop();
312 return res;
313 }
314
315 fn strip_test_functions(diagnostic: &errors::Handler, krate: ast::Crate)
316 -> ast::Crate {
317 // When not compiling with --test we should not compile the
318 // #[test] functions
319 config::strip_items(diagnostic, krate, |attrs| {
320 !attr::contains_name(&attrs[..], "test") &&
321 !attr::contains_name(&attrs[..], "bench")
322 })
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 let info = ExpnInfo {
330 call_site: DUMMY_SP,
331 callee: NameAndSpan {
332 format: MacroAttribute(intern("test")),
333 span: None,
334 allow_internal_unstable: true,
335 }
336 };
337 let expn_id = cx.sess.codemap().record_expansion(info);
338 let mut sp = sp;
339 sp.expn_id = expn_id;
340 return sp;
341 }
342
343 #[derive(PartialEq)]
344 enum HasTestSignature {
345 Yes,
346 No,
347 NotEvenAFunction,
348 }
349
350 fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
351 let has_test_attr = attr::contains_name(&i.attrs, "test");
352
353 fn has_test_signature(i: &ast::Item) -> HasTestSignature {
354 match i.node {
355 ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
356 let no_output = match decl.output {
357 ast::FunctionRetTy::Default(..) => true,
358 ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
359 _ => false
360 };
361 if decl.inputs.is_empty()
362 && no_output
363 && !generics.is_parameterized() {
364 Yes
365 } else {
366 No
367 }
368 }
369 _ => NotEvenAFunction,
370 }
371 }
372
373 if has_test_attr {
374 let diag = cx.span_diagnostic;
375 match has_test_signature(i) {
376 Yes => {},
377 No => diag.span_err(i.span, "functions used as tests must have signature fn() -> ()"),
378 NotEvenAFunction => diag.span_err(i.span,
379 "only functions may be used as tests"),
380 }
381 }
382
383 return has_test_attr && has_test_signature(i) == Yes;
384 }
385
386 fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
387 let has_bench_attr = attr::contains_name(&i.attrs, "bench");
388
389 fn has_test_signature(i: &ast::Item) -> bool {
390 match i.node {
391 ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
392 let input_cnt = decl.inputs.len();
393 let no_output = match decl.output {
394 ast::FunctionRetTy::Default(..) => true,
395 ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
396 _ => false
397 };
398 let tparm_cnt = generics.ty_params.len();
399 // NB: inadequate check, but we're running
400 // well before resolve, can't get too deep.
401 input_cnt == 1
402 && no_output && tparm_cnt == 0
403 }
404 _ => false
405 }
406 }
407
408 if has_bench_attr && !has_test_signature(i) {
409 let diag = cx.span_diagnostic;
410 diag.span_err(i.span, "functions used as benches must have signature \
411 `fn(&mut Bencher) -> ()`");
412 }
413
414 return has_bench_attr && has_test_signature(i);
415 }
416
417 fn is_ignored(i: &ast::Item) -> bool {
418 i.attrs.iter().any(|attr| attr.check_name("ignore"))
419 }
420
421 fn should_panic(i: &ast::Item) -> ShouldPanic {
422 match i.attrs.iter().find(|attr| attr.check_name("should_panic")) {
423 Some(attr) => {
424 let msg = attr.meta_item_list()
425 .and_then(|list| list.iter().find(|mi| mi.check_name("expected")))
426 .and_then(|mi| mi.value_str());
427 ShouldPanic::Yes(msg)
428 }
429 None => ShouldPanic::No,
430 }
431 }
432
433 /*
434
435 We're going to be building a module that looks more or less like:
436
437 mod __test {
438 extern crate test (name = "test", vers = "...");
439 fn main() {
440 test::test_main_static(&::os::args()[], tests)
441 }
442
443 static tests : &'static [test::TestDescAndFn] = &[
444 ... the list of tests in the crate ...
445 ];
446 }
447
448 */
449
450 fn mk_std(cx: &TestCtxt) -> P<ast::Item> {
451 let id_test = token::str_to_ident("test");
452 let (vi, vis, ident) = if cx.is_test_crate {
453 (ast::ItemKind::Use(
454 P(nospan(ast::ViewPathSimple(id_test,
455 path_node(vec!(id_test)))))),
456 ast::Visibility::Public, token::special_idents::invalid)
457 } else {
458 (ast::ItemKind::ExternCrate(None), ast::Visibility::Inherited, id_test)
459 };
460 P(ast::Item {
461 id: ast::DUMMY_NODE_ID,
462 ident: ident,
463 node: vi,
464 attrs: vec![],
465 vis: vis,
466 span: DUMMY_SP
467 })
468 }
469
470 fn mk_main(cx: &mut TestCtxt) -> P<ast::Item> {
471 // Writing this out by hand with 'ignored_span':
472 // pub fn main() {
473 // #![main]
474 // use std::slice::AsSlice;
475 // test::test_main_static(::std::os::args().as_slice(), TESTS);
476 // }
477
478 let sp = ignored_span(cx, DUMMY_SP);
479 let ecx = &cx.ext_cx;
480
481 // test::test_main_static
482 let test_main_path = ecx.path(sp, vec![token::str_to_ident("test"),
483 token::str_to_ident("test_main_static")]);
484 // test::test_main_static(...)
485 let test_main_path_expr = ecx.expr_path(test_main_path);
486 let tests_ident_expr = ecx.expr_ident(sp, token::str_to_ident("TESTS"));
487 let call_test_main = ecx.expr_call(sp, test_main_path_expr,
488 vec![tests_ident_expr]);
489 let call_test_main = ecx.stmt_expr(call_test_main);
490 // #![main]
491 let main_meta = ecx.meta_word(sp, token::intern_and_get_ident("main"));
492 let main_attr = ecx.attribute(sp, main_meta);
493 // pub fn main() { ... }
494 let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
495 let main_body = ecx.block_all(sp, vec![call_test_main], None);
496 let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], main_ret_ty),
497 ast::Unsafety::Normal,
498 ast::Constness::NotConst,
499 ::abi::Abi::Rust, ast::Generics::default(), main_body);
500 let main = P(ast::Item {
501 ident: token::str_to_ident("main"),
502 attrs: vec![main_attr],
503 id: ast::DUMMY_NODE_ID,
504 node: main,
505 vis: ast::Visibility::Public,
506 span: sp
507 });
508
509 return main;
510 }
511
512 fn mk_test_module(cx: &mut TestCtxt) -> (P<ast::Item>, Option<P<ast::Item>>) {
513 // Link to test crate
514 let import = mk_std(cx);
515
516 // A constant vector of test descriptors.
517 let tests = mk_tests(cx);
518
519 // The synthesized main function which will call the console test runner
520 // with our list of tests
521 let mainfn = mk_main(cx);
522
523 let testmod = ast::Mod {
524 inner: DUMMY_SP,
525 items: vec![import, mainfn, tests],
526 };
527 let item_ = ast::ItemKind::Mod(testmod);
528
529 let mod_ident = token::gensym_ident("__test");
530 let item = P(ast::Item {
531 id: ast::DUMMY_NODE_ID,
532 ident: mod_ident,
533 attrs: vec![],
534 node: item_,
535 vis: ast::Visibility::Public,
536 span: DUMMY_SP,
537 });
538 let reexport = cx.reexport_test_harness_main.as_ref().map(|s| {
539 // building `use <ident> = __test::main`
540 let reexport_ident = token::str_to_ident(&s);
541
542 let use_path =
543 nospan(ast::ViewPathSimple(reexport_ident,
544 path_node(vec![mod_ident, token::str_to_ident("main")])));
545
546 P(ast::Item {
547 id: ast::DUMMY_NODE_ID,
548 ident: token::special_idents::invalid,
549 attrs: vec![],
550 node: ast::ItemKind::Use(P(use_path)),
551 vis: ast::Visibility::Inherited,
552 span: DUMMY_SP
553 })
554 });
555
556 debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item));
557
558 (item, reexport)
559 }
560
561 fn nospan<T>(t: T) -> codemap::Spanned<T> {
562 codemap::Spanned { node: t, span: DUMMY_SP }
563 }
564
565 fn path_node(ids: Vec<ast::Ident> ) -> ast::Path {
566 ast::Path {
567 span: DUMMY_SP,
568 global: false,
569 segments: ids.into_iter().map(|identifier| ast::PathSegment {
570 identifier: identifier,
571 parameters: ast::PathParameters::none(),
572 }).collect()
573 }
574 }
575
576 fn path_name_i(idents: &[ast::Ident]) -> String {
577 // FIXME: Bad copies (#2543 -- same for everything else that says "bad")
578 idents.iter().map(|i| i.to_string()).collect::<Vec<String>>().join("::")
579 }
580
581 fn mk_tests(cx: &TestCtxt) -> P<ast::Item> {
582 // The vector of test_descs for this crate
583 let test_descs = mk_test_descs(cx);
584
585 // FIXME #15962: should be using quote_item, but that stringifies
586 // __test_reexports, causing it to be reinterned, losing the
587 // gensym information.
588 let sp = DUMMY_SP;
589 let ecx = &cx.ext_cx;
590 let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"),
591 ecx.ident_of("test"),
592 ecx.ident_of("TestDescAndFn")]));
593 let static_lt = ecx.lifetime(sp, token::special_idents::static_lifetime.name);
594 // &'static [self::test::TestDescAndFn]
595 let static_type = ecx.ty_rptr(sp,
596 ecx.ty(sp, ast::TyKind::Vec(struct_type)),
597 Some(static_lt),
598 ast::Mutability::Immutable);
599 // static TESTS: $static_type = &[...];
600 ecx.item_const(sp,
601 ecx.ident_of("TESTS"),
602 static_type,
603 test_descs)
604 }
605
606 fn is_test_crate(krate: &ast::Crate) -> bool {
607 match attr::find_crate_name(&krate.attrs) {
608 Some(ref s) if "test" == &s[..] => true,
609 _ => false
610 }
611 }
612
613 fn mk_test_descs(cx: &TestCtxt) -> P<ast::Expr> {
614 debug!("building test vector from {} tests", cx.testfns.len());
615
616 P(ast::Expr {
617 id: ast::DUMMY_NODE_ID,
618 node: ast::ExprKind::AddrOf(ast::Mutability::Immutable,
619 P(ast::Expr {
620 id: ast::DUMMY_NODE_ID,
621 node: ast::ExprKind::Vec(cx.testfns.iter().map(|test| {
622 mk_test_desc_and_fn_rec(cx, test)
623 }).collect()),
624 span: DUMMY_SP,
625 attrs: None,
626 })),
627 span: DUMMY_SP,
628 attrs: None,
629 })
630 }
631
632 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> P<ast::Expr> {
633 // FIXME #15962: should be using quote_expr, but that stringifies
634 // __test_reexports, causing it to be reinterned, losing the
635 // gensym information.
636
637 let span = ignored_span(cx, test.span);
638 let path = test.path.clone();
639 let ecx = &cx.ext_cx;
640 let self_id = ecx.ident_of("self");
641 let test_id = ecx.ident_of("test");
642
643 // creates self::test::$name
644 let test_path = |name| {
645 ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)])
646 };
647 // creates $name: $expr
648 let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr);
649
650 debug!("encoding {}", path_name_i(&path[..]));
651
652 // path to the #[test] function: "foo::bar::baz"
653 let path_string = path_name_i(&path[..]);
654 let name_expr = ecx.expr_str(span, token::intern_and_get_ident(&path_string[..]));
655
656 // self::test::StaticTestName($name_expr)
657 let name_expr = ecx.expr_call(span,
658 ecx.expr_path(test_path("StaticTestName")),
659 vec![name_expr]);
660
661 let ignore_expr = ecx.expr_bool(span, test.ignore);
662 let should_panic_path = |name| {
663 ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)])
664 };
665 let fail_expr = match test.should_panic {
666 ShouldPanic::No => ecx.expr_path(should_panic_path("No")),
667 ShouldPanic::Yes(ref msg) => {
668 match *msg {
669 Some(ref msg) => {
670 let msg = ecx.expr_str(span, msg.clone());
671 let path = should_panic_path("YesWithMessage");
672 ecx.expr_call(span, ecx.expr_path(path), vec![msg])
673 }
674 None => ecx.expr_path(should_panic_path("Yes")),
675 }
676 }
677 };
678
679 // self::test::TestDesc { ... }
680 let desc_expr = ecx.expr_struct(
681 span,
682 test_path("TestDesc"),
683 vec![field("name", name_expr),
684 field("ignore", ignore_expr),
685 field("should_panic", fail_expr)]);
686
687
688 let mut visible_path = match cx.toplevel_reexport {
689 Some(id) => vec![id],
690 None => {
691 let diag = cx.span_diagnostic;
692 diag.bug("expected to find top-level re-export name, but found None");
693 }
694 };
695 visible_path.extend(path);
696
697 let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path));
698
699 let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" };
700 // self::test::$variant_name($fn_expr)
701 let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]);
702
703 // self::test::TestDescAndFn { ... }
704 ecx.expr_struct(span,
705 test_path("TestDescAndFn"),
706 vec![field("desc", desc_expr),
707 field("testfn", testfn_expr)])
708 }