1 // Code that generates a test runner to run all the tests in a crate
3 import core::{option, vec};
4 import syntax::{ast, ast_util};
5 import syntax::ast_util::*;
6 //import syntax::ast_util::dummy_sp;
8 import syntax::print::pprust;
9 import syntax::codemap::span;
10 import driver::session;
13 export modify_for_testing;
15 type node_id_gen = fn@() -> ast::node_id;
17 type test = {span: span, path: [ast::ident], ignore: bool, should_fail: bool};
20 @{sess: session::session,
22 mutable path: [ast::ident],
23 mutable testfns: [test]};
25 // Traverse the crate, collecting all the test functions, eliding any
26 // existing main functions, and synthesizing a main test harness
27 fn modify_for_testing(sess: session::session,
28 crate: @ast::crate) -> @ast::crate {
30 if sess.get_opts().test {
31 generate_test_harness(sess, crate)
33 strip_test_functions(crate)
37 fn generate_test_harness(sess: session::session,
38 crate: @ast::crate) -> @ast::crate {
46 {fold_crate: bind fold_crate(cx, _, _),
47 fold_item: bind fold_item(cx, _, _),
48 fold_mod: bind fold_mod(cx, _, _) with *fold::default_ast_fold()};
50 let fold = fold::make_fold(precursor);
51 let res = @fold.fold_crate(*crate);
55 fn strip_test_functions(crate: @ast::crate) -> @ast::crate {
56 // When not compiling with --test we should not compile the
58 config::strip_items(crate) {|attrs|
59 !attr::contains_name(attr::attr_metas(attrs), "test")
63 fn fold_mod(_cx: test_ctxt, m: ast::_mod, fld: fold::ast_fold) -> ast::_mod {
65 // Remove any defined main function from the AST so it doesn't clash with
66 // the one we're going to add. FIXME: This is sloppy. Instead we should
67 // have some mechanism to indicate to the translation pass which function
68 // we want to be main.
69 fn nomain(&&item: @ast::item) -> option::t<@ast::item> {
71 ast::item_fn(_, _, _) {
72 if item.ident == "main" {
74 } else { option::some(item) }
76 _ { option::some(item) }
81 {view_items: m.view_items, items: vec::filter_map(m.items, nomain)};
82 ret fold::noop_fold_mod(mod_nomain, fld);
85 fn fold_crate(cx: test_ctxt, c: ast::crate_, fld: fold::ast_fold) ->
87 let folded = fold::noop_fold_crate(c, fld);
89 // Add a special __test module to the crate that will contain code
90 // generated for the test harness
91 ret {module: add_test_module(cx, folded.module) with folded};
95 fn fold_item(cx: test_ctxt, &&i: @ast::item, fld: fold::ast_fold) ->
99 #debug("current path: %s", ast_util::path_name_i(cx.path));
103 ast::item_fn(decl, _, _) when decl.purity == ast::unsafe_fn {
106 "unsafe functions cannot be used for tests");
109 #debug("this is a test function");
110 let test = {span: i.span,
111 path: cx.path, ignore: is_ignored(cx, i),
112 should_fail: should_fail(i)};
113 cx.testfns += [test];
114 #debug("have %u test functions", vec::len(cx.testfns));
119 let res = fold::noop_fold_item(i, fld);
124 fn is_test_fn(i: @ast::item) -> bool {
126 vec::len(attr::find_attrs_by_name(i.attrs, "test")) > 0u;
128 fn has_test_signature(i: @ast::item) -> bool {
130 ast::item_fn(decl, tps, _) {
131 let input_cnt = vec::len(decl.inputs);
132 let no_output = decl.output.node == ast::ty_nil;
133 let tparm_cnt = vec::len(tps);
134 input_cnt == 0u && no_output && tparm_cnt == 0u
140 ret has_test_attr && has_test_signature(i);
143 fn is_ignored(cx: test_ctxt, i: @ast::item) -> bool {
144 let ignoreattrs = attr::find_attrs_by_name(i.attrs, "ignore");
145 let ignoreitems = attr::attr_metas(ignoreattrs);
146 let cfg_metas = vec::concat(vec::filter_map(ignoreitems,
147 {|&&i| attr::get_meta_item_list(i)}));
148 ret if vec::is_not_empty(ignoreitems) {
149 config::metas_in_cfg(cx.crate.node.config, cfg_metas)
155 fn should_fail(i: @ast::item) -> bool {
156 vec::len(attr::find_attrs_by_name(i.attrs, "should_fail")) > 0u
159 fn add_test_module(cx: test_ctxt, m: ast::_mod) -> ast::_mod {
160 let testmod = mk_test_module(cx);
161 ret {items: m.items + [testmod] with m};
166 We're going to be building a module that looks more or less like:
170 fn main(args: [str]) -> int {
171 std::test::test_main(args, tests())
174 fn tests() -> [std::test::test_desc] {
175 ... the list of tests in the crate ...
181 fn mk_test_module(cx: test_ctxt) -> @ast::item {
182 // A function that generates a vector of test descriptors to feed to the
184 let testsfn = mk_tests(cx);
185 // The synthesized main function which will call the console test runner
186 // with our list of tests
187 let mainfn = mk_main(cx);
188 let testmod: ast::_mod = {view_items: [], items: [mainfn, testsfn]};
189 let item_ = ast::item_mod(testmod);
190 let item: ast::item =
193 id: cx.sess.next_node_id(),
197 #debug("Synthetic test module:\n%s\n", pprust::item_to_str(@item));
202 fn nospan<T: copy>(t: T) -> ast::spanned<T> {
203 ret {node: t, span: dummy_sp()};
206 fn mk_tests(cx: test_ctxt) -> @ast::item {
207 let ret_ty = mk_test_desc_vec_ty(cx);
209 let decl: ast::fn_decl =
212 purity: ast::impure_fn,
216 // The vector of test_descs for this crate
217 let test_descs = mk_test_desc_vec(cx);
219 let body_: ast::blk_ =
220 default_block([], option::some(test_descs), cx.sess.next_node_id());
221 let body = nospan(body_);
223 let item_ = ast::item_fn(decl, [], body);
224 let item: ast::item =
227 id: cx.sess.next_node_id(),
233 // The ast::ty of [std::test::test_desc]
234 fn mk_test_desc_vec_ty(cx: test_ctxt) -> @ast::ty {
235 let test_fn_ty: ast::ty = nospan(
239 idents: ["std", "test", "default_test_fn"],
242 cx.sess.next_node_id()));
244 let test_desc_ty_path =
245 @nospan({global: false,
246 idents: ["std", "test", "test_desc"],
247 types: [@test_fn_ty]});
249 let test_desc_ty: ast::ty =
250 nospan(ast::ty_path(test_desc_ty_path, cx.sess.next_node_id()));
252 let vec_mt: ast::mt = {ty: @test_desc_ty, mut: ast::imm};
254 ret @nospan(ast::ty_vec(vec_mt));
257 fn mk_test_desc_vec(cx: test_ctxt) -> @ast::expr {
258 #debug("building test vector from %u tests", vec::len(cx.testfns));
260 for test: test in cx.testfns {
261 let test_ = test; // Satisfy alias analysis
262 descs += [mk_test_desc_rec(cx, test_)];
265 ret @{id: cx.sess.next_node_id(),
266 node: ast::expr_vec(descs, ast::imm),
270 fn mk_test_desc_rec(cx: test_ctxt, test: test) -> @ast::expr {
271 let span = test.span;
272 let path = test.path;
274 #debug("encoding %s", ast_util::path_name_i(path));
276 let name_lit: ast::lit =
277 nospan(ast::lit_str(ast_util::path_name_i(path)));
278 let name_expr: ast::expr =
279 {id: cx.sess.next_node_id(),
280 node: ast::expr_lit(@name_lit),
283 let name_field: ast::field =
284 nospan({mut: ast::imm, ident: "name", expr: @name_expr});
286 let fn_path = @nospan({global: false, idents: path, types: []});
288 let fn_expr: ast::expr =
289 {id: cx.sess.next_node_id(),
290 node: ast::expr_path(fn_path),
293 let fn_wrapper_expr = mk_test_wrapper(cx, fn_expr, span);
295 let fn_field: ast::field =
296 nospan({mut: ast::imm, ident: "fn", expr: fn_wrapper_expr});
298 let ignore_lit: ast::lit = nospan(ast::lit_bool(test.ignore));
300 let ignore_expr: ast::expr =
301 {id: cx.sess.next_node_id(),
302 node: ast::expr_lit(@ignore_lit),
305 let ignore_field: ast::field =
306 nospan({mut: ast::imm, ident: "ignore", expr: @ignore_expr});
308 let fail_lit: ast::lit = nospan(ast::lit_bool(test.should_fail));
310 let fail_expr: ast::expr =
311 {id: cx.sess.next_node_id(),
312 node: ast::expr_lit(@fail_lit),
315 let fail_field: ast::field =
316 nospan({mut: ast::imm, ident: "should_fail", expr: @fail_expr});
318 let desc_rec_: ast::expr_ =
319 ast::expr_rec([name_field, fn_field, ignore_field, fail_field],
321 let desc_rec: ast::expr =
322 {id: cx.sess.next_node_id(), node: desc_rec_, span: span};
326 // Produces a bare function that wraps the test function
327 // FIXME: This can go away once fn is the type of bare function
328 fn mk_test_wrapper(cx: test_ctxt,
329 fn_path_expr: ast::expr,
330 span: span) -> @ast::expr {
331 let call_expr: ast::expr = {
332 id: cx.sess.next_node_id(),
333 node: ast::expr_call(@fn_path_expr, [], false),
337 let call_stmt: ast::stmt = nospan(
338 ast::stmt_semi(@call_expr, cx.sess.next_node_id()));
340 let wrapper_decl: ast::fn_decl = {
342 output: @nospan(ast::ty_nil),
343 purity: ast::impure_fn,
348 let wrapper_body: ast::blk = nospan({
352 id: cx.sess.next_node_id(),
353 rules: ast::default_blk
356 let wrapper_capture: @ast::capture_clause = @{
361 let wrapper_expr: ast::expr = {
362 id: cx.sess.next_node_id(),
363 node: ast::expr_fn(ast::proto_bare, wrapper_decl,
364 wrapper_body, wrapper_capture),
371 fn mk_main(cx: test_ctxt) -> @ast::item {
373 let args_mt: ast::mt = {ty: @nospan(ast::ty_str), mut: ast::imm};
374 let args_ty: ast::ty = nospan(ast::ty_vec(args_mt));
376 let args_arg: ast::arg =
380 id: cx.sess.next_node_id()};
382 let ret_ty = nospan(ast::ty_nil);
384 let decl: ast::fn_decl =
387 purity: ast::impure_fn,
391 let test_main_call_expr = mk_test_main_call(cx);
393 let body_: ast::blk_ =
394 default_block([], option::some(test_main_call_expr),
395 cx.sess.next_node_id());
396 let body = {node: body_, span: dummy_sp()};
398 let item_ = ast::item_fn(decl, [], body);
399 let item: ast::item =
402 id: cx.sess.next_node_id(),
408 fn mk_test_main_call(cx: test_ctxt) -> @ast::expr {
410 // Get the args passed to main so we can pass the to test_main
412 @nospan({global: false, idents: ["args"], types: []});
414 let args_path_expr_: ast::expr_ = ast::expr_path(args_path);
416 let args_path_expr: ast::expr =
417 {id: cx.sess.next_node_id(), node: args_path_expr_, span: dummy_sp()};
419 // Call __test::test to generate the vector of test_descs
421 @nospan({global: false, idents: ["tests"], types: []});
423 let test_path_expr_: ast::expr_ = ast::expr_path(test_path);
425 let test_path_expr: ast::expr =
426 {id: cx.sess.next_node_id(), node: test_path_expr_, span: dummy_sp()};
428 let test_call_expr_ = ast::expr_call(@test_path_expr, [], false);
430 let test_call_expr: ast::expr =
431 {id: cx.sess.next_node_id(), node: test_call_expr_, span: dummy_sp()};
433 // Call std::test::test_main
435 @nospan({global: false,
436 idents: ["std", "test", "test_main"],
439 let test_main_path_expr_: ast::expr_ = ast::expr_path(test_main_path);
441 let test_main_path_expr: ast::expr =
442 {id: cx.sess.next_node_id(), node: test_main_path_expr_,
445 let test_main_call_expr_: ast::expr_ =
446 ast::expr_call(@test_main_path_expr,
447 [@args_path_expr, @test_call_expr], false);
449 let test_main_call_expr: ast::expr =
450 {id: cx.sess.next_node_id(), node: test_main_call_expr_,
453 ret @test_main_call_expr;
459 // indent-tabs-mode: nil
461 // buffer-file-coding-system: utf-8-unix