1 // Copyright 2012 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
16 use syntax::ast_util::*;
18 use syntax::codemap::{dummy_sp, span, ExpandedFrom, CallInfo, NameAndSpan};
20 use syntax::ext::base::{mk_ctxt, ext_ctxt};
22 use syntax::print::pprust;
23 use syntax::{ast, ast_util};
25 type node_id_gen = @fn() -> ast::node_id;
36 sess: session::Session,
43 // Traverse the crate, collecting all the test functions, eliding any
44 // existing main functions, and synthesizing a main test harness
45 pub fn modify_for_testing(sess: session::Session,
48 // We generate the test harness when building in the 'test'
49 // configuration, either with the '--test' or '--cfg test'
50 // command line options.
51 let should_test = attr::contains(crate.node.config,
52 attr::mk_word_item(@~"test"));
55 generate_test_harness(sess, crate)
57 strip_test_functions(crate)
61 fn generate_test_harness(sess: session::Session,
64 let cx: @mut TestCtxt = @mut TestCtxt {
67 ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
72 cx.ext_cx.bt_push(ExpandedFrom(CallInfo {
73 call_site: dummy_sp(),
80 let precursor = @fold::AstFoldFns {
81 fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
82 fold_item: |a,b| fold_item(cx, a, b),
83 fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};
85 let fold = fold::make_fold(precursor);
86 let res = @fold.fold_crate(&*crate);
91 fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
92 // When not compiling with --test we should not compile the
94 do config::strip_items(crate) |attrs| {
95 !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
96 !attr::contains_name(attr::attr_metas(attrs), ~"bench")
100 fn fold_mod(cx: @mut TestCtxt,
102 fld: @fold::ast_fold)
104 // Remove any #[main] from the AST so it doesn't clash with
105 // the one we're going to add. Only if compiling an executable.
107 fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
108 if !*cx.sess.building_library {
109 @ast::item{attrs: item.attrs.filtered(|attr| {
110 *attr::get_attr_name(attr) != ~"main"
115 let mod_nomain = ast::_mod {
116 view_items: /*bad*/copy m.view_items,
117 items: vec::map(m.items, |i| nomain(cx, *i)),
120 fold::noop_fold_mod(&mod_nomain, fld)
123 fn fold_crate(cx: @mut TestCtxt,
125 fld: @fold::ast_fold)
127 let folded = fold::noop_fold_crate(c, fld);
129 // Add a special __test module to the crate that will contain code
130 // generated for the test harness
132 module: add_test_module(cx, &folded.module),
138 fn fold_item(cx: @mut TestCtxt, i: @ast::item, fld: @fold::ast_fold)
139 -> Option<@ast::item> {
140 cx.path.push(i.ident);
141 debug!("current path: %s",
142 ast_util::path_name_i(copy cx.path, cx.sess.parse_sess.interner));
144 if is_test_fn(i) || is_bench_fn(i) {
146 ast::item_fn(_, purity, _, _, _) if purity == ast::unsafe_fn => {
150 ~"unsafe functions cannot be used for tests");
153 debug!("this is a test function");
156 path: /*bad*/copy cx.path,
157 bench: is_bench_fn(i),
158 ignore: is_ignored(cx, i),
159 should_fail: should_fail(i)
161 cx.testfns.push(test);
162 // debug!("have %u test/bench functions", cx.testfns.len());
167 let res = fold::noop_fold_item(i, fld);
172 fn is_test_fn(i: @ast::item) -> bool {
173 let has_test_attr = !attr::find_attrs_by_name(i.attrs,
176 fn has_test_signature(i: @ast::item) -> bool {
178 &ast::item_fn(ref decl, _, _, ref generics, _) => {
179 let no_output = match decl.output.node {
183 decl.inputs.is_empty()
185 && !generics.is_parameterized()
191 return has_test_attr && has_test_signature(i);
194 fn is_bench_fn(i: @ast::item) -> bool {
196 vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;
198 fn has_test_signature(i: @ast::item) -> bool {
200 ast::item_fn(ref decl, _, _, ref generics, _) => {
201 let input_cnt = vec::len(decl.inputs);
202 let no_output = match decl.output.node {
206 let tparm_cnt = generics.ty_params.len();
207 // NB: inadequate check, but we're running
208 // well before resolve, can't get too deep.
210 && no_output && tparm_cnt == 0u
216 return has_bench_attr && has_test_signature(i);
219 fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
220 let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
221 let ignoreitems = attr::attr_metas(ignoreattrs);
222 return if !ignoreitems.is_empty() {
225 vec::filter_map(ignoreitems,
226 |i| attr::get_meta_item_list(i)));
227 config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
233 fn should_fail(i: @ast::item) -> bool {
234 vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
237 fn add_test_module(cx: &TestCtxt, m: &ast::_mod) -> ast::_mod {
238 let testmod = mk_test_module(cx);
240 items: vec::append_one(/*bad*/copy m.items, testmod),
247 We're going to be building a module that looks more or less like:
250 #[!resolve_unexported]
251 extern mod std (name = "std", vers = "...");
254 std::test::test_main_static(::os::args(), tests)
257 static tests : &'static [std::test::TestDescAndFn] = &[
258 ... the list of tests in the crate ...
264 fn mk_std(cx: &TestCtxt) -> @ast::view_item {
265 let vers = ast::lit_str(@~"0.7-pre");
266 let vers = nospan(vers);
267 let mi = ast::meta_name_value(@~"vers", vers);
269 let id_std = cx.sess.ident_of(~"std");
270 let vi = if is_std(cx) {
272 ~[@nospan(ast::view_path_simple(id_std,
273 path_node(~[id_std]),
274 cx.sess.next_node_id()))])
276 ast::view_item_extern_mod(id_std, ~[@mi],
277 cx.sess.next_node_id())
279 let vi = ast::view_item {
288 fn mk_test_module(cx: &TestCtxt) -> @ast::item {
291 let view_items = ~[mk_std(cx)];
293 // A constant vector of test descriptors.
294 let tests = mk_tests(cx);
296 // The synthesized main function which will call the console test runner
297 // with our list of tests
298 let ext_cx = cx.ext_cx;
299 let mainfn = (quote_item!(
302 std::test::test_main_static(::os::args(), tests);
306 let testmod = ast::_mod {
307 view_items: view_items,
308 items: ~[mainfn, tests],
310 let item_ = ast::item_mod(testmod);
312 // This attribute tells resolve to let us call unexported functions
313 let resolve_unexported_attr =
314 attr::mk_attr(attr::mk_word_item(@~"!resolve_unexported"));
316 let item = ast::item {
317 ident: cx.sess.ident_of(~"__test"),
318 attrs: ~[resolve_unexported_attr],
319 id: cx.sess.next_node_id(),
325 debug!("Synthetic test module:\n%s\n",
326 pprust::item_to_str(@copy item, cx.sess.intr()));
331 fn nospan<T:Copy>(t: T) -> codemap::spanned<T> {
332 codemap::spanned { node: t, span: dummy_sp() }
335 fn path_node(ids: ~[ast::ident]) -> @ast::Path {
336 @ast::Path { span: dummy_sp(),
343 fn path_node_global(ids: ~[ast::ident]) -> @ast::Path {
344 @ast::Path { span: dummy_sp(),
351 fn mk_tests(cx: &TestCtxt) -> @ast::item {
353 let ext_cx = cx.ext_cx;
355 // The vector of test_descs for this crate
356 let test_descs = mk_test_descs(cx);
359 pub static tests : &'static [self::std::test::TestDescAndFn] =
365 fn is_std(cx: &TestCtxt) -> bool {
367 let items = attr::find_linkage_metas(cx.crate.node.attrs);
368 match attr::last_meta_item_value_str_by_name(items, ~"name") {
369 Some(@~"std") => true,
376 fn mk_test_descs(cx: &TestCtxt) -> @ast::expr {
377 debug!("building test vector from %u tests", cx.testfns.len());
379 for cx.testfns.each |test| {
380 descs.push(mk_test_desc_and_fn_rec(cx, test));
384 let inner_expr = @ast::expr {
385 id: sess.next_node_id(),
386 callee_id: sess.next_node_id(),
387 node: ast::expr_vec(descs, ast::m_imm),
392 id: sess.next_node_id(),
393 callee_id: sess.next_node_id(),
394 node: ast::expr_vstore(inner_expr, ast::expr_vstore_slice),
399 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: &Test) -> @ast::expr {
400 let span = test.span;
401 let path = /*bad*/copy test.path;
403 let ext_cx = cx.ext_cx;
405 debug!("encoding %s", ast_util::path_name_i(path,
406 cx.sess.parse_sess.interner));
408 let name_lit: ast::lit =
409 nospan(ast::lit_str(@ast_util::path_name_i(
411 cx.sess.parse_sess.interner)));
413 let name_expr = @ast::expr {
414 id: cx.sess.next_node_id(),
415 callee_id: cx.sess.next_node_id(),
416 node: ast::expr_lit(@name_lit),
420 let fn_path = path_node_global(path);
422 let fn_expr = @ast::expr {
423 id: cx.sess.next_node_id(),
424 callee_id: cx.sess.next_node_id(),
425 node: ast::expr_path(fn_path),
429 let t_expr = if test.bench {
430 quote_expr!( self::std::test::StaticBenchFn($fn_expr) )
432 quote_expr!( self::std::test::StaticTestFn($fn_expr) )
435 let ignore_expr = if test.ignore {
441 let fail_expr = if test.should_fail {
448 self::std::test::TestDescAndFn {
449 desc: self::std::test::TestDesc {
450 name: self::std::test::StaticTestName($name_expr),
451 ignore: $ignore_expr,
452 should_fail: $fail_expr
463 // indent-tabs-mode: nil
465 // buffer-file-coding-system: utf-8-unix