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
22 use syntax::ast_util::*;
24 use syntax::codemap::{dummy_sp, span, ExpandedFrom, CallInfo, NameAndSpan};
27 use syntax::print::pprust;
28 use syntax::{ast, ast_util};
29 use syntax::attr::attrs_contains_name;
31 use syntax::ext::base::{mk_ctxt, ext_ctxt};
33 type node_id_gen = fn@() -> ast::node_id;
44 sess: session::Session,
51 // Traverse the crate, collecting all the test functions, eliding any
52 // existing main functions, and synthesizing a main test harness
53 pub fn modify_for_testing(sess: session::Session,
56 // We generate the test harness when building in the 'test'
57 // configuration, either with the '--test' or '--cfg test'
58 // command line options.
59 let should_test = attr::contains(crate.node.config,
60 attr::mk_word_item(@~"test"));
63 generate_test_harness(sess, crate)
65 strip_test_functions(crate)
69 fn generate_test_harness(sess: session::Session,
72 let cx: @mut TestCtxt = @mut TestCtxt {
75 ext_cx: mk_ctxt(sess.parse_sess, copy sess.opts.cfg),
80 cx.ext_cx.bt_push(ExpandedFrom(CallInfo {
81 call_site: dummy_sp(),
88 let precursor = @fold::AstFoldFns {
89 fold_crate: fold::wrap(|a,b| fold_crate(cx, a, b) ),
90 fold_item: |a,b| fold_item(cx, a, b),
91 fold_mod: |a,b| fold_mod(cx, a, b),.. *fold::default_ast_fold()};
93 let fold = fold::make_fold(precursor);
94 let res = @fold.fold_crate(*crate);
99 fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
100 // When not compiling with --test we should not compile the
102 do config::strip_items(crate) |attrs| {
103 !attr::contains_name(attr::attr_metas(attrs), ~"test") &&
104 !attr::contains_name(attr::attr_metas(attrs), ~"bench")
108 fn fold_mod(cx: @mut TestCtxt,
112 // Remove any #[main] from the AST so it doesn't clash with
113 // the one we're going to add. Only if compiling an executable.
115 fn nomain(cx: @mut TestCtxt, item: @ast::item) -> @ast::item {
116 if !*cx.sess.building_library {
117 @ast::item{attrs: item.attrs.filtered(|attr| {
118 *attr::get_attr_name(attr) != ~"main"
123 let mod_nomain = ast::_mod {
124 view_items: /*bad*/copy m.view_items,
125 items: vec::map(m.items, |i| nomain(cx, *i)),
128 fold::noop_fold_mod(mod_nomain, fld)
131 fn fold_crate(cx: @mut TestCtxt,
135 let folded = fold::noop_fold_crate(c, fld);
137 // Add a special __test module to the crate that will contain code
138 // generated for the test harness
139 ast::crate_ { module: add_test_module(cx, /*bad*/copy folded.module),
144 fn fold_item(cx: @mut TestCtxt, &&i: @ast::item, fld: fold::ast_fold)
145 -> Option<@ast::item> {
146 cx.path.push(i.ident);
147 debug!("current path: %s",
148 ast_util::path_name_i(cx.path, cx.sess.parse_sess.interner));
150 if is_test_fn(i) || is_bench_fn(i) {
152 ast::item_fn(_, purity, _, _) if purity == ast::unsafe_fn => {
156 ~"unsafe functions cannot be used for tests");
159 debug!("this is a test function");
162 path: /*bad*/copy cx.path,
163 bench: is_bench_fn(i),
164 ignore: is_ignored(cx, i),
165 should_fail: should_fail(i)
167 cx.testfns.push(test);
168 debug!("have %u test/bench functions", cx.testfns.len());
173 let res = fold::noop_fold_item(i, fld);
178 fn is_test_fn(i: @ast::item) -> bool {
179 let has_test_attr = !attr::find_attrs_by_name(i.attrs,
182 fn has_test_signature(i: @ast::item) -> bool {
184 &ast::item_fn(ref decl, _, ref tps, _) => {
185 let no_output = match decl.output.node {
189 decl.inputs.is_empty() && no_output && tps.is_empty()
195 return has_test_attr && has_test_signature(i);
198 fn is_bench_fn(i: @ast::item) -> bool {
200 vec::len(attr::find_attrs_by_name(i.attrs, ~"bench")) > 0u;
202 fn has_test_signature(i: @ast::item) -> bool {
203 match /*bad*/copy i.node {
204 ast::item_fn(decl, _, tps, _) => {
205 let input_cnt = vec::len(decl.inputs);
206 let no_output = match decl.output.node {
210 let tparm_cnt = vec::len(tps);
211 // NB: inadequate check, but we're running
212 // well before resolve, can't get too deep.
214 && no_output && tparm_cnt == 0u
220 return has_bench_attr && has_test_signature(i);
223 fn is_ignored(cx: @mut TestCtxt, i: @ast::item) -> bool {
224 let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
225 let ignoreitems = attr::attr_metas(ignoreattrs);
226 return if !ignoreitems.is_empty() {
229 vec::filter_map(ignoreitems,
230 |i| attr::get_meta_item_list(i)));
231 config::metas_in_cfg(/*bad*/copy cx.crate.node.config, cfg_metas)
237 fn should_fail(i: @ast::item) -> bool {
238 vec::len(attr::find_attrs_by_name(i.attrs, ~"should_fail")) > 0u
241 fn add_test_module(cx: &TestCtxt, +m: ast::_mod) -> ast::_mod {
242 let testmod = mk_test_module(cx);
244 items: vec::append_one(/*bad*/copy m.items, testmod),
251 We're going to be building a module that looks more or less like:
254 #[!resolve_unexported]
255 extern mod std (name = "std", vers = "...");
258 std::test::test_main_static(::os::args(), tests)
261 const tests : &static/[std::test::TestDescAndFn] = &[
262 ... the list of tests in the crate ...
268 fn mk_std(cx: &TestCtxt) -> @ast::view_item {
269 let vers = ast::lit_str(@~"0.6");
270 let vers = nospan(vers);
271 let mi = ast::meta_name_value(@~"vers", vers);
273 let id_std = cx.sess.ident_of(~"std");
274 let vi = if is_std(cx) {
276 ~[@nospan(ast::view_path_simple(id_std,
277 path_node(~[id_std]),
279 cx.sess.next_node_id()))])
281 ast::view_item_extern_mod(id_std, ~[@mi],
282 cx.sess.next_node_id())
284 let vi = ast::view_item {
293 fn mk_test_module(cx: &TestCtxt) -> @ast::item {
296 let view_items = ~[mk_std(cx)];
298 // A constant vector of test descriptors.
299 let tests = mk_tests(cx);
301 // The synthesized main function which will call the console test runner
302 // with our list of tests
303 let ext_cx = cx.ext_cx;
304 let mainfn = (quote_item!(
307 std::test::test_main_static(::os::args(), tests);
311 let testmod = ast::_mod {
312 view_items: view_items,
313 items: ~[mainfn, tests],
315 let item_ = ast::item_mod(testmod);
317 // This attribute tells resolve to let us call unexported functions
318 let resolve_unexported_attr =
319 attr::mk_attr(attr::mk_word_item(@~"!resolve_unexported"));
321 let item = ast::item {
322 ident: cx.sess.ident_of(~"__test"),
323 attrs: ~[resolve_unexported_attr],
324 id: cx.sess.next_node_id(),
330 debug!("Synthetic test module:\n%s\n",
331 pprust::item_to_str(@copy item, cx.sess.intr()));
336 fn nospan<T:Copy>(t: T) -> codemap::spanned<T> {
337 codemap::spanned { node: t, span: dummy_sp() }
340 fn path_node(+ids: ~[ast::ident]) -> @ast::path {
341 @ast::path { span: dummy_sp(),
348 fn path_node_global(+ids: ~[ast::ident]) -> @ast::path {
349 @ast::path { span: dummy_sp(),
357 fn mk_tests(cx: &TestCtxt) -> @ast::item {
359 let ext_cx = cx.ext_cx;
361 // The vector of test_descs for this crate
362 let test_descs = mk_test_descs(cx);
365 pub const tests : &static/[self::std::test::TestDescAndFn] =
371 fn is_std(cx: &TestCtxt) -> bool {
373 let items = attr::find_linkage_metas(cx.crate.node.attrs);
374 match attr::last_meta_item_value_str_by_name(items, ~"name") {
375 Some(@~"std") => true,
382 fn mk_test_descs(cx: &TestCtxt) -> @ast::expr {
383 debug!("building test vector from %u tests", cx.testfns.len());
385 for cx.testfns.each |test| {
386 descs.push(mk_test_desc_and_fn_rec(cx, *test));
390 let inner_expr = @ast::expr {
391 id: sess.next_node_id(),
392 callee_id: sess.next_node_id(),
393 node: ast::expr_vec(descs, ast::m_imm),
398 id: sess.next_node_id(),
399 callee_id: sess.next_node_id(),
400 node: ast::expr_vstore(inner_expr, ast::expr_vstore_slice),
405 fn mk_test_desc_and_fn_rec(cx: &TestCtxt, test: Test) -> @ast::expr {
406 let span = test.span;
407 let path = /*bad*/copy test.path;
409 let ext_cx = cx.ext_cx;
411 debug!("encoding %s", ast_util::path_name_i(path,
412 cx.sess.parse_sess.interner));
414 let name_lit: ast::lit =
415 nospan(ast::lit_str(@ast_util::path_name_i(
417 cx.sess.parse_sess.interner)));
419 let name_expr = @ast::expr {
420 id: cx.sess.next_node_id(),
421 callee_id: cx.sess.next_node_id(),
422 node: ast::expr_lit(@name_lit),
426 let fn_path = path_node_global(path);
428 let fn_expr = @ast::expr {
429 id: cx.sess.next_node_id(),
430 callee_id: cx.sess.next_node_id(),
431 node: ast::expr_path(fn_path),
435 let t_expr = if test.bench {
436 quote_expr!( self::std::test::StaticBenchFn($fn_expr) )
438 quote_expr!( self::std::test::StaticTestFn($fn_expr) )
441 let ignore_expr = if test.ignore {
447 let fail_expr = if test.should_fail {
454 self::std::test::TestDescAndFn {
455 desc: self::std::test::TestDesc {
456 name: self::std::test::StaticTestName($name_expr),
457 ignore: $ignore_expr,
458 should_fail: $fail_expr
469 // indent-tabs-mode: nil
471 // buffer-file-coding-system: utf-8-unix