]> git.proxmox.com Git - rustc.git/blame - src/libsyntax/test.rs
Imported Upstream version 1.9.0+dfsg1
[rustc.git] / src / libsyntax / test.rs
CommitLineData
1a4d82fc
JJ
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)]
15use self::HasTestSignature::*;
16
e9174d1e 17use std::iter;
1a4d82fc
JJ
18use std::slice;
19use std::mem;
20use std::vec;
1a4d82fc
JJ
21use attr::AttrMetaMethods;
22use attr;
23use codemap::{DUMMY_SP, Span, ExpnInfo, NameAndSpan, MacroAttribute};
24use codemap;
9cc50fc6 25use errors;
1a4d82fc 26use config;
e9174d1e 27use entry::{self, EntryPointType};
1a4d82fc
JJ
28use ext::base::ExtCtxt;
29use ext::build::AstBuilder;
30use ext::expand::ExpansionConfig;
92a42be0
SL
31use fold::Folder;
32use util::move_map::MoveMap;
1a4d82fc 33use fold;
e9174d1e 34use parse::token::{intern, InternedString};
1a4d82fc
JJ
35use parse::{token, ParseSess};
36use print::pprust;
54a0048b 37use ast;
1a4d82fc
JJ
38use ptr::P;
39use util::small_vector::SmallVector;
40
c34b1796 41enum ShouldPanic {
1a4d82fc
JJ
42 No,
43 Yes(Option<InternedString>),
44}
45
46struct Test {
47 span: Span,
48 path: Vec<ast::Ident> ,
49 bench: bool,
50 ignore: bool,
c34b1796 51 should_panic: ShouldPanic
1a4d82fc
JJ
52}
53
54struct TestCtxt<'a> {
55 sess: &'a ParseSess,
9cc50fc6 56 span_diagnostic: &'a errors::Handler,
1a4d82fc
JJ
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
70pub fn modify_for_testing(sess: &ParseSess,
71 cfg: &ast::CrateConfig,
72 krate: ast::Crate,
9cc50fc6 73 span_diagnostic: &errors::Handler) -> ast::Crate {
1a4d82fc
JJ
74 // We generate the test harness when building in the 'test'
75 // configuration, either with the '--test' or '--cfg test'
76 // command line options.
c34b1796 77 let should_test = attr::contains_name(&krate.config, "test");
1a4d82fc
JJ
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 =
c34b1796 84 attr::first_attr_value_str_by_name(&krate.attrs,
1a4d82fc
JJ
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 {
92a42be0 90 strip_test_functions(span_diagnostic, krate)
1a4d82fc
JJ
91 }
92}
93
94struct 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
102impl<'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);
1a4d82fc 109 match reexport {
85aaf69f 110 Some(re) => folded.module.items.push(re),
1a4d82fc
JJ
111 None => {}
112 }
85aaf69f 113 folded.module.items.push(mod_);
1a4d82fc
JJ
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 }
54a0048b 122 debug!("current path: {}", path_name_i(&self.cx.path));
1a4d82fc 123
7453a54e 124 let i = if is_test_fn(&self.cx, &i) || is_bench_fn(&self.cx, &i) {
1a4d82fc 125 match i.node {
7453a54e 126 ast::ItemKind::Fn(_, ast::Unsafety::Unsafe, _, _, _, _) => {
1a4d82fc 127 let diag = self.cx.span_diagnostic;
9346a6ac 128 panic!(diag.span_fatal(i.span, "unsafe functions cannot be used for tests"));
1a4d82fc
JJ
129 }
130 _ => {
131 debug!("this is a test function");
132 let test = Test {
133 span: i.span,
134 path: self.cx.path.clone(),
7453a54e
SL
135 bench: is_bench_fn(&self.cx, &i),
136 ignore: is_ignored(&i),
137 should_panic: should_panic(&i)
1a4d82fc
JJ
138 };
139 self.cx.testfns.push(test);
140 self.tests.push(i.ident);
141 // debug!("have {} test/bench functions",
142 // cx.testfns.len());
c34b1796
AL
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| {
7453a54e 148 i.vis = ast::Visibility::Public;
c34b1796
AL
149 i
150 })
1a4d82fc
JJ
151 }
152 }
c34b1796
AL
153 } else {
154 i
155 };
1a4d82fc
JJ
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 {
7453a54e 160 ast::ItemKind::Mod(..) => fold::noop_fold_item(i, self),
1a4d82fc
JJ
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
1a4d82fc
JJ
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
e9174d1e
SL
192struct EntryPointCleaner {
193 // Current depth in the ast
194 depth: usize,
195}
196
197impl 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.
7453a54e 206 let folded = match entry::entry_point_type(&folded, self.depth) {
e9174d1e
SL
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
1a4d82fc
JJ
241fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec<ast::Ident>,
242 tested_submods: Vec<(ast::Ident, ast::Ident)>) -> (P<ast::Item>, ast::Ident) {
1a4d82fc
JJ
243 let super_ = token::str_to_ident("super");
244
85aaf69f 245 let items = tests.into_iter().map(|r| {
7453a54e 246 cx.ext_cx.item_use_simple(DUMMY_SP, ast::Visibility::Public,
1a4d82fc 247 cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
85aaf69f 248 }).chain(tested_submods.into_iter().map(|(r, sym)| {
1a4d82fc 249 let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, sym]);
7453a54e 250 cx.ext_cx.item_use_simple_(DUMMY_SP, ast::Visibility::Public, r, path)
1a4d82fc
JJ
251 }));
252
253 let reexport_mod = ast::Mod {
254 inner: DUMMY_SP,
85aaf69f 255 items: items.collect(),
1a4d82fc
JJ
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,
7453a54e
SL
263 node: ast::ItemKind::Mod(reexport_mod),
264 vis: ast::Visibility::Public,
1a4d82fc
JJ
265 span: DUMMY_SP,
266 });
267
268 (it, sym)
269}
270
271fn generate_test_harness(sess: &ParseSess,
272 reexport_test_harness_main: Option<InternedString>,
273 krate: ast::Crate,
274 cfg: &ast::CrateConfig,
9cc50fc6 275 sd: &errors::Handler) -> ast::Crate {
e9174d1e
SL
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![];
1a4d82fc
JJ
281 let mut cx: TestCtxt = TestCtxt {
282 sess: sess,
283 span_diagnostic: sd,
284 ext_cx: ExtCtxt::new(sess, cfg.clone(),
e9174d1e
SL
285 ExpansionConfig::default("test".to_string()),
286 &mut feature_gated_cfgs),
1a4d82fc
JJ
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 };
e9174d1e 294 cx.ext_cx.crate_root = Some("std");
1a4d82fc
JJ
295
296 cx.ext_cx.bt_push(ExpnInfo {
297 call_site: DUMMY_SP,
298 callee: NameAndSpan {
e9174d1e 299 format: MacroAttribute(intern("test")),
c34b1796
AL
300 span: None,
301 allow_internal_unstable: false,
1a4d82fc
JJ
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
9cc50fc6 315fn strip_test_functions(diagnostic: &errors::Handler, krate: ast::Crate)
92a42be0 316 -> ast::Crate {
1a4d82fc
JJ
317 // When not compiling with --test we should not compile the
318 // #[test] functions
92a42be0 319 config::strip_items(diagnostic, krate, |attrs| {
85aaf69f
SL
320 !attr::contains_name(&attrs[..], "test") &&
321 !attr::contains_name(&attrs[..], "bench")
1a4d82fc
JJ
322 })
323}
324
85aaf69f
SL
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.
328fn ignored_span(cx: &TestCtxt, sp: Span) -> Span {
329 let info = ExpnInfo {
330 call_site: DUMMY_SP,
331 callee: NameAndSpan {
e9174d1e 332 format: MacroAttribute(intern("test")),
c34b1796
AL
333 span: None,
334 allow_internal_unstable: true,
85aaf69f
SL
335 }
336 };
62682a34 337 let expn_id = cx.sess.codemap().record_expansion(info);
85aaf69f
SL
338 let mut sp = sp;
339 sp.expn_id = expn_id;
340 return sp;
341}
342
1a4d82fc
JJ
343#[derive(PartialEq)]
344enum HasTestSignature {
345 Yes,
346 No,
347 NotEvenAFunction,
348}
349
1a4d82fc 350fn is_test_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
c34b1796 351 let has_test_attr = attr::contains_name(&i.attrs, "test");
1a4d82fc
JJ
352
353 fn has_test_signature(i: &ast::Item) -> HasTestSignature {
92a42be0 354 match i.node {
7453a54e 355 ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
1a4d82fc 356 let no_output = match decl.output {
7453a54e
SL
357 ast::FunctionRetTy::Default(..) => true,
358 ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
85aaf69f 359 _ => false
1a4d82fc
JJ
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
386fn is_bench_fn(cx: &TestCtxt, i: &ast::Item) -> bool {
c34b1796 387 let has_bench_attr = attr::contains_name(&i.attrs, "bench");
1a4d82fc
JJ
388
389 fn has_test_signature(i: &ast::Item) -> bool {
390 match i.node {
7453a54e 391 ast::ItemKind::Fn(ref decl, _, _, _, ref generics, _) => {
1a4d82fc
JJ
392 let input_cnt = decl.inputs.len();
393 let no_output = match decl.output {
7453a54e
SL
394 ast::FunctionRetTy::Default(..) => true,
395 ast::FunctionRetTy::Ty(ref t) if t.node == ast::TyKind::Tup(vec![]) => true,
85aaf69f 396 _ => false
1a4d82fc
JJ
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.
85aaf69f
SL
401 input_cnt == 1
402 && no_output && tparm_cnt == 0
1a4d82fc
JJ
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
417fn is_ignored(i: &ast::Item) -> bool {
418 i.attrs.iter().any(|attr| attr.check_name("ignore"))
419}
420
c34b1796
AL
421fn should_panic(i: &ast::Item) -> ShouldPanic {
422 match i.attrs.iter().find(|attr| attr.check_name("should_panic")) {
1a4d82fc
JJ
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());
c34b1796 427 ShouldPanic::Yes(msg)
1a4d82fc 428 }
c34b1796 429 None => ShouldPanic::No,
1a4d82fc
JJ
430 }
431}
432
433/*
434
435We're going to be building a module that looks more or less like:
436
437mod __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
85aaf69f 450fn mk_std(cx: &TestCtxt) -> P<ast::Item> {
1a4d82fc 451 let id_test = token::str_to_ident("test");
85aaf69f 452 let (vi, vis, ident) = if cx.is_test_crate {
7453a54e 453 (ast::ItemKind::Use(
1a4d82fc 454 P(nospan(ast::ViewPathSimple(id_test,
85aaf69f 455 path_node(vec!(id_test)))))),
7453a54e 456 ast::Visibility::Public, token::special_idents::invalid)
1a4d82fc 457 } else {
7453a54e 458 (ast::ItemKind::ExternCrate(None), ast::Visibility::Inherited, id_test)
1a4d82fc 459 };
85aaf69f
SL
460 P(ast::Item {
461 id: ast::DUMMY_NODE_ID,
462 ident: ident,
1a4d82fc 463 node: vi,
85aaf69f 464 attrs: vec![],
1a4d82fc
JJ
465 vis: vis,
466 span: DUMMY_SP
85aaf69f 467 })
1a4d82fc
JJ
468}
469
85aaf69f
SL
470fn 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")]);
85aaf69f
SL
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,
e9174d1e 488 vec![tests_ident_expr]);
85aaf69f
SL
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() { ... }
7453a54e 494 let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(vec![]));
85aaf69f 495 let main_body = ecx.block_all(sp, vec![call_test_main], None);
7453a54e 496 let main = ast::ItemKind::Fn(ecx.fn_decl(vec![], main_ret_ty),
62682a34
SL
497 ast::Unsafety::Normal,
498 ast::Constness::NotConst,
7453a54e 499 ::abi::Abi::Rust, ast::Generics::default(), main_body);
85aaf69f
SL
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,
7453a54e 505 vis: ast::Visibility::Public,
85aaf69f
SL
506 span: sp
507 });
508
509 return main;
510}
511
512fn mk_test_module(cx: &mut TestCtxt) -> (P<ast::Item>, Option<P<ast::Item>>) {
1a4d82fc 513 // Link to test crate
85aaf69f 514 let import = mk_std(cx);
1a4d82fc
JJ
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
85aaf69f 521 let mainfn = mk_main(cx);
1a4d82fc
JJ
522
523 let testmod = ast::Mod {
524 inner: DUMMY_SP,
85aaf69f 525 items: vec![import, mainfn, tests],
1a4d82fc 526 };
7453a54e 527 let item_ = ast::ItemKind::Mod(testmod);
1a4d82fc
JJ
528
529 let mod_ident = token::gensym_ident("__test");
85aaf69f 530 let item = P(ast::Item {
1a4d82fc 531 id: ast::DUMMY_NODE_ID,
85aaf69f
SL
532 ident: mod_ident,
533 attrs: vec![],
1a4d82fc 534 node: item_,
7453a54e 535 vis: ast::Visibility::Public,
1a4d82fc 536 span: DUMMY_SP,
85aaf69f 537 });
1a4d82fc
JJ
538 let reexport = cx.reexport_test_harness_main.as_ref().map(|s| {
539 // building `use <ident> = __test::main`
85aaf69f 540 let reexport_ident = token::str_to_ident(&s);
1a4d82fc
JJ
541
542 let use_path =
543 nospan(ast::ViewPathSimple(reexport_ident,
85aaf69f 544 path_node(vec![mod_ident, token::str_to_ident("main")])));
1a4d82fc 545
85aaf69f
SL
546 P(ast::Item {
547 id: ast::DUMMY_NODE_ID,
548 ident: token::special_idents::invalid,
1a4d82fc 549 attrs: vec![],
7453a54e
SL
550 node: ast::ItemKind::Use(P(use_path)),
551 vis: ast::Visibility::Inherited,
1a4d82fc 552 span: DUMMY_SP
85aaf69f 553 })
1a4d82fc
JJ
554 });
555
7453a54e 556 debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item));
1a4d82fc 557
85aaf69f 558 (item, reexport)
1a4d82fc
JJ
559}
560
561fn nospan<T>(t: T) -> codemap::Spanned<T> {
562 codemap::Spanned { node: t, span: DUMMY_SP }
563}
564
565fn 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
54a0048b
SL
576fn 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
1a4d82fc
JJ
581fn 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,
7453a54e 596 ecx.ty(sp, ast::TyKind::Vec(struct_type)),
1a4d82fc 597 Some(static_lt),
7453a54e 598 ast::Mutability::Immutable);
1a4d82fc
JJ
599 // static TESTS: $static_type = &[...];
600 ecx.item_const(sp,
601 ecx.ident_of("TESTS"),
602 static_type,
603 test_descs)
604}
605
606fn is_test_crate(krate: &ast::Crate) -> bool {
c34b1796 607 match attr::find_crate_name(&krate.attrs) {
85aaf69f 608 Some(ref s) if "test" == &s[..] => true,
1a4d82fc
JJ
609 _ => false
610 }
611}
612
613fn 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,
7453a54e 618 node: ast::ExprKind::AddrOf(ast::Mutability::Immutable,
1a4d82fc
JJ
619 P(ast::Expr {
620 id: ast::DUMMY_NODE_ID,
7453a54e 621 node: ast::ExprKind::Vec(cx.testfns.iter().map(|test| {
1a4d82fc
JJ
622 mk_test_desc_and_fn_rec(cx, test)
623 }).collect()),
624 span: DUMMY_SP,
92a42be0 625 attrs: None,
1a4d82fc
JJ
626 })),
627 span: DUMMY_SP,
92a42be0 628 attrs: None,
1a4d82fc
JJ
629 })
630}
631
632fn 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
85aaf69f 637 let span = ignored_span(cx, test.span);
1a4d82fc
JJ
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
85aaf69f 644 let test_path = |name| {
1a4d82fc
JJ
645 ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)])
646 };
647 // creates $name: $expr
85aaf69f 648 let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr);
1a4d82fc 649
54a0048b 650 debug!("encoding {}", path_name_i(&path[..]));
1a4d82fc
JJ
651
652 // path to the #[test] function: "foo::bar::baz"
54a0048b 653 let path_string = path_name_i(&path[..]);
85aaf69f 654 let name_expr = ecx.expr_str(span, token::intern_and_get_ident(&path_string[..]));
1a4d82fc
JJ
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);
c34b1796
AL
662 let should_panic_path = |name| {
663 ecx.path(span, vec![self_id, test_id, ecx.ident_of("ShouldPanic"), ecx.ident_of(name)])
1a4d82fc 664 };
c34b1796
AL
665 let fail_expr = match test.should_panic {
666 ShouldPanic::No => ecx.expr_path(should_panic_path("No")),
667 ShouldPanic::Yes(ref msg) => {
e9174d1e
SL
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 }
1a4d82fc
JJ
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),
c34b1796 685 field("should_panic", fail_expr)]);
1a4d82fc
JJ
686
687
688 let mut visible_path = match cx.toplevel_reexport {
689 Some(id) => vec![id],
690 None => {
691 let diag = cx.span_diagnostic;
9cc50fc6 692 diag.bug("expected to find top-level re-export name, but found None");
1a4d82fc
JJ
693 }
694 };
62682a34 695 visible_path.extend(path);
1a4d82fc
JJ
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}