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.
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.
11 // Code that generates a test runner to run all the tests in a crate
14 #![allow(unused_imports)]
16 use driver::session::Session;
19 use std::gc::{Gc, GC};
23 use syntax::ast_util::*;
24 use syntax::attr::AttrMetaMethods;
26 use syntax::codemap::{DUMMY_SP, Span, ExpnInfo, NameAndSpan, MacroAttribute};
28 use syntax::ext::base::ExtCtxt;
29 use syntax::ext::build::AstBuilder;
30 use syntax::ext::expand::ExpansionConfig;
31 use syntax::fold::Folder;
33 use syntax::owned_slice::OwnedSlice;
34 use syntax::parse::token::InternedString;
35 use syntax::parse::token;
36 use syntax::print::pprust;
37 use syntax::{ast, ast_util};
38 use syntax::util::small_vector::SmallVector;
42 path: Vec<ast::Ident> ,
50 path: Vec<ast::Ident>,
53 reexport_mod_ident: ast::Ident,
54 reexport_test_harness_main: Option<InternedString>,
56 config: ast::CrateConfig,
59 // Traverse the crate, collecting all the test functions, eliding any
60 // existing main functions, and synthesizing a main test harness
61 pub fn modify_for_testing(sess: &Session,
62 krate: ast::Crate) -> ast::Crate {
63 // We generate the test harness when building in the 'test'
64 // configuration, either with the '--test' or '--cfg test'
65 // command line options.
66 let should_test = attr::contains_name(krate.config.as_slice(), "test");
68 // Check for #[reexport_test_harness_main = "some_name"] which
69 // creates a `use some_name = __test::main;`. This needs to be
70 // unconditional, so that the attribute is still marked as used in
72 let reexport_test_harness_main =
73 attr::first_attr_value_str_by_name(krate.attrs.as_slice(),
74 "reexport_test_harness_main");
77 generate_test_harness(sess, reexport_test_harness_main, krate)
79 strip_test_functions(krate)
83 struct TestHarnessGenerator<'a> {
85 tests: Vec<ast::Ident>,
86 tested_submods: Vec<ast::Ident>,
89 impl<'a> fold::Folder for TestHarnessGenerator<'a> {
90 fn fold_crate(&mut self, c: ast::Crate) -> ast::Crate {
91 let mut folded = fold::noop_fold_crate(c, self);
93 // Add a special __test module to the crate that will contain code
94 // generated for the test harness
95 let (mod_, reexport) = mk_test_module(&self.cx, &self.cx.reexport_test_harness_main);
96 folded.module.items.push(mod_);
98 Some(re) => folded.module.view_items.push(re),
104 fn fold_item(&mut self, i: Gc<ast::Item>) -> SmallVector<Gc<ast::Item>> {
105 self.cx.path.push(i.ident);
106 debug!("current path: {}",
107 ast_util::path_name_i(self.cx.path.as_slice()));
109 if is_test_fn(&self.cx, i) || is_bench_fn(&self.cx, i) {
111 ast::ItemFn(_, ast::UnsafeFn, _, _, _) => {
112 let sess = self.cx.sess;
113 sess.span_fatal(i.span,
114 "unsafe functions cannot be used for \
118 debug!("this is a test function");
121 path: self.cx.path.clone(),
122 bench: is_bench_fn(&self.cx, i),
123 ignore: is_ignored(&self.cx, i),
124 should_fail: should_fail(i)
126 self.cx.testfns.push(test);
127 self.tests.push(i.ident);
128 // debug!("have {} test/bench functions",
129 // cx.testfns.len());
134 // We don't want to recurse into anything other than mods, since
135 // mods or tests inside of functions will break things
136 let res = match i.node {
137 ast::ItemMod(..) => fold::noop_fold_item(&*i, self),
138 _ => SmallVector::one(i),
144 fn fold_mod(&mut self, m: &ast::Mod) -> ast::Mod {
145 let tests = mem::replace(&mut self.tests, Vec::new());
146 let tested_submods = mem::replace(&mut self.tested_submods, Vec::new());
147 let mut mod_folded = fold::noop_fold_mod(m, self);
148 let tests = mem::replace(&mut self.tests, tests);
149 let tested_submods = mem::replace(&mut self.tested_submods, tested_submods);
151 // Remove any #[main] from the AST so it doesn't clash with
152 // the one we're going to add. Only if compiling an executable.
154 fn nomain(item: Gc<ast::Item>) -> Gc<ast::Item> {
156 attrs: item.attrs.iter().filter_map(|attr| {
157 if !attr.check_name("main") {
167 for i in mod_folded.items.mut_iter() {
170 if !tests.is_empty() || !tested_submods.is_empty() {
171 mod_folded.items.push(mk_reexport_mod(&mut self.cx, tests,
173 if !self.cx.path.is_empty() {
174 self.tested_submods.push(self.cx.path[self.cx.path.len()-1]);
182 fn mk_reexport_mod(cx: &mut TestCtxt, tests: Vec<ast::Ident>,
183 tested_submods: Vec<ast::Ident>) -> Gc<ast::Item> {
184 let mut view_items = Vec::new();
185 let super_ = token::str_to_ident("super");
187 view_items.extend(tests.move_iter().map(|r| {
188 cx.ext_cx.view_use_simple(DUMMY_SP, ast::Public,
189 cx.ext_cx.path(DUMMY_SP, vec![super_, r]))
191 view_items.extend(tested_submods.move_iter().map(|r| {
192 let path = cx.ext_cx.path(DUMMY_SP, vec![super_, r, cx.reexport_mod_ident]);
193 cx.ext_cx.view_use_simple_(DUMMY_SP, ast::Public, r, path)
196 let reexport_mod = ast::Mod {
198 view_items: view_items,
202 ident: cx.reexport_mod_ident.clone(),
204 id: ast::DUMMY_NODE_ID,
205 node: ast::ItemMod(reexport_mod),
211 fn generate_test_harness(sess: &Session,
212 reexport_test_harness_main: Option<InternedString>,
213 krate: ast::Crate) -> ast::Crate {
214 let mut cx: TestCtxt = TestCtxt {
216 ext_cx: ExtCtxt::new(&sess.parse_sess, sess.opts.cfg.clone(),
218 deriving_hash_type_parameter: false,
219 crate_name: "test".to_string(),
223 reexport_mod_ident: token::gensym_ident("__test_reexports"),
224 reexport_test_harness_main: reexport_test_harness_main,
225 is_test_crate: is_test_crate(&krate),
226 config: krate.config.clone(),
229 cx.ext_cx.bt_push(ExpnInfo {
231 callee: NameAndSpan {
232 name: "test".to_string(),
233 format: MacroAttribute,
238 let mut fold = TestHarnessGenerator {
241 tested_submods: Vec::new(),
243 let res = fold.fold_crate(krate);
244 fold.cx.ext_cx.bt_pop();
248 fn strip_test_functions(krate: ast::Crate) -> ast::Crate {
249 // When not compiling with --test we should not compile the
251 config::strip_items(krate, |attrs| {
252 !attr::contains_name(attrs.as_slice(), "test") &&
253 !attr::contains_name(attrs.as_slice(), "bench")
257 fn is_test_fn(cx: &TestCtxt, i: Gc<ast::Item>) -> bool {
258 let has_test_attr = attr::contains_name(i.attrs.as_slice(), "test");
260 fn has_test_signature(i: Gc<ast::Item>) -> bool {
262 &ast::ItemFn(ref decl, _, _, ref generics, _) => {
263 let no_output = match decl.output.node {
267 decl.inputs.is_empty()
269 && !generics.is_parameterized()
275 if has_test_attr && !has_test_signature(i) {
279 "functions used as tests must have signature fn() -> ()."
283 return has_test_attr && has_test_signature(i);
286 fn is_bench_fn(cx: &TestCtxt, i: Gc<ast::Item>) -> bool {
287 let has_bench_attr = attr::contains_name(i.attrs.as_slice(), "bench");
289 fn has_test_signature(i: Gc<ast::Item>) -> bool {
291 ast::ItemFn(ref decl, _, _, ref generics, _) => {
292 let input_cnt = decl.inputs.len();
293 let no_output = match decl.output.node {
297 let tparm_cnt = generics.ty_params.len();
298 // NB: inadequate check, but we're running
299 // well before resolve, can't get too deep.
301 && no_output && tparm_cnt == 0u
307 if has_bench_attr && !has_test_signature(i) {
309 sess.span_err(i.span, "functions used as benches must have signature \
310 `fn(&mut Bencher) -> ()`");
313 return has_bench_attr && has_test_signature(i);
316 fn is_ignored(cx: &TestCtxt, i: Gc<ast::Item>) -> bool {
317 i.attrs.iter().any(|attr| {
318 // check ignore(cfg(foo, bar))
319 attr.check_name("ignore") && match attr.meta_item_list() {
321 attr::test_cfg(cx.config.as_slice(), cfgs.iter().map(|x| *x))
328 fn should_fail(i: Gc<ast::Item>) -> bool {
329 attr::contains_name(i.attrs.as_slice(), "should_fail")
334 We're going to be building a module that looks more or less like:
337 extern crate test (name = "test", vers = "...");
339 test::test_main_static(::os::args().as_slice(), tests)
342 static tests : &'static [test::TestDescAndFn] = &[
343 ... the list of tests in the crate ...
349 fn mk_std(cx: &TestCtxt) -> ast::ViewItem {
350 let id_test = token::str_to_ident("test");
351 let (vi, vis) = if cx.is_test_crate {
353 box(GC) nospan(ast::ViewPathSimple(id_test,
354 path_node(vec!(id_test)),
355 ast::DUMMY_NODE_ID))),
358 (ast::ViewItemExternCrate(id_test, None, ast::DUMMY_NODE_ID),
369 fn mk_test_module(cx: &TestCtxt, reexport_test_harness_main: &Option<InternedString>)
370 -> (Gc<ast::Item>, Option<ast::ViewItem>) {
371 // Link to test crate
372 let view_items = vec!(mk_std(cx));
374 // A constant vector of test descriptors.
375 let tests = mk_tests(cx);
377 // The synthesized main function which will call the console test runner
378 // with our list of tests
379 let mainfn = (quote_item!(&cx.ext_cx,
382 use std::slice::Vector;
383 test::test_main_static(::std::os::args().as_slice(), TESTS);
387 let testmod = ast::Mod {
389 view_items: view_items,
390 items: vec!(mainfn, tests),
392 let item_ = ast::ItemMod(testmod);
394 let mod_ident = token::gensym_ident("__test");
395 let item = ast::Item {
398 id: ast::DUMMY_NODE_ID,
403 let reexport = reexport_test_harness_main.as_ref().map(|s| {
404 // building `use <ident> = __test::main`
405 let reexport_ident = token::str_to_ident(s.get());
408 nospan(ast::ViewPathSimple(reexport_ident,
409 path_node(vec![mod_ident, token::str_to_ident("main")]),
410 ast::DUMMY_NODE_ID));
413 node: ast::ViewItemUse(box(GC) use_path),
420 debug!("Synthetic test module:\n{}\n", pprust::item_to_string(&item));
422 (box(GC) item, reexport)
425 fn nospan<T>(t: T) -> codemap::Spanned<T> {
426 codemap::Spanned { node: t, span: DUMMY_SP }
429 fn path_node(ids: Vec<ast::Ident> ) -> ast::Path {
433 segments: ids.move_iter().map(|identifier| ast::PathSegment {
434 identifier: identifier,
435 lifetimes: Vec::new(),
436 types: OwnedSlice::empty(),
441 fn mk_tests(cx: &TestCtxt) -> Gc<ast::Item> {
442 // The vector of test_descs for this crate
443 let test_descs = mk_test_descs(cx);
445 // FIXME #15962: should be using quote_item, but that stringifies
446 // __test_reexports, causing it to be reinterned, losing the
447 // gensym information.
449 let ecx = &cx.ext_cx;
450 let struct_type = ecx.ty_path(ecx.path(sp, vec![ecx.ident_of("self"),
451 ecx.ident_of("test"),
452 ecx.ident_of("TestDescAndFn")]),
454 let static_lt = ecx.lifetime(sp, token::special_idents::static_lifetime.name);
455 // &'static [self::test::TestDescAndFn]
456 let static_type = ecx.ty_rptr(sp,
457 ecx.ty(sp, ast::TyVec(struct_type)),
460 // static TESTS: $static_type = &[...];
462 ecx.ident_of("TESTS"),
468 fn is_test_crate(krate: &ast::Crate) -> bool {
469 match attr::find_crate_name(krate.attrs.as_slice()) {
470 Some(ref s) if "test" == s.get().as_slice() => true,
475 fn mk_test_descs(cx: &TestCtxt) -> Gc<ast::Expr> {
476 debug!("building test vector from {} tests", cx.testfns.len());
479 id: ast::DUMMY_NODE_ID,
480 node: ast::ExprVstore(box(GC) ast::Expr {
481 id: ast::DUMMY_NODE_ID,
482 node: ast::ExprVec(cx.testfns.iter().map(|test| {
483 mk_test_desc_and_fn_rec(cx, test)
486 }, ast::ExprVstoreSlice),
491 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> Gc<ast::Expr> {
492 // FIXME #15962: should be using quote_expr, but that stringifies
493 // __test_reexports, causing it to be reinterned, losing the
494 // gensym information.
496 let span = test.span;
497 let path = test.path.clone();
498 let ecx = &cx.ext_cx;
499 let self_id = ecx.ident_of("self");
500 let test_id = ecx.ident_of("test");
502 // creates self::test::$name
503 let test_path = |name| {
504 ecx.path(span, vec![self_id, test_id, ecx.ident_of(name)])
506 // creates $name: $expr
507 let field = |name, expr| ecx.field_imm(span, ecx.ident_of(name), expr);
509 debug!("encoding {}", ast_util::path_name_i(path.as_slice()));
511 // path to the #[test] function: "foo::bar::baz"
512 let path_string = ast_util::path_name_i(path.as_slice());
513 let name_expr = ecx.expr_str(span, token::intern_and_get_ident(path_string.as_slice()));
515 // self::test::StaticTestName($name_expr)
516 let name_expr = ecx.expr_call(span,
517 ecx.expr_path(test_path("StaticTestName")),
520 let ignore_expr = ecx.expr_bool(span, test.ignore);
521 let fail_expr = ecx.expr_bool(span, test.should_fail);
523 // self::test::TestDesc { ... }
524 let desc_expr = ecx.expr_struct(
526 test_path("TestDesc"),
527 vec![field("name", name_expr),
528 field("ignore", ignore_expr),
529 field("should_fail", fail_expr)]);
532 let mut visible_path = vec![cx.reexport_mod_ident.clone()];
533 visible_path.extend(path.move_iter());
535 let fn_expr = ecx.expr_path(ecx.path_global(span, visible_path));
537 let variant_name = if test.bench { "StaticBenchFn" } else { "StaticTestFn" };
538 // self::test::$variant_name($fn_expr)
539 let testfn_expr = ecx.expr_call(span, ecx.expr_path(test_path(variant_name)), vec![fn_expr]);
541 // self::test::TestDescAndFn { ... }
542 ecx.expr_struct(span,
543 test_path("TestDescAndFn"),
544 vec![field("desc", desc_expr),
545 field("testfn", testfn_expr)])