From b3dee955144a722da50e17dde62cb36cbcccf73f Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 14 Jul 2011 11:29:54 -0700 Subject: [PATCH] Add a facility for ignoring tests. Issue #428 Adding the #[ignore] attribute will cause the test not to be run, though it will still show up in the list of tests. --- src/comp/front/test.rs | 39 ++++++++++++++++----- src/lib/test.rs | 69 ++++++++++++++++++++++++++++--------- src/test/stdtest/stdtest.rc | 1 + src/test/stdtest/test.rs | 36 +++++++++++++++++++ 4 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 src/test/stdtest/test.rs diff --git a/src/comp/front/test.rs b/src/comp/front/test.rs index 939d2c717da..a0d1118bdd0 100644 --- a/src/comp/front/test.rs +++ b/src/comp/front/test.rs @@ -11,9 +11,12 @@ export modify_for_testing; type node_id_gen = @fn() -> ast::node_id; +type test = rec(ast::ident[] path, + bool ignore); + type test_ctxt = @rec(node_id_gen next_node_id, mutable ast::ident[] path, - mutable ast::ident[][] testfns); + mutable test[] testfns); // Traverse the crate, collecting all the test functions, eliding any // existing main functions, and synthesizing a main test harness @@ -88,7 +91,9 @@ fn fold_item(&test_ctxt cx, &@ast::item i, if (is_test_fn(i)) { log "this is a test function"; - cx.testfns += ~[cx.path]; + auto test = rec(path = cx.path, + ignore = is_ignored(i)); + cx.testfns += ~[test]; log #fmt("have %u test functions", ivec::len(cx.testfns)); } @@ -116,6 +121,10 @@ fn is_test_fn(&@ast::item i) -> bool { ret has_test_attr && has_test_signature(i); } +fn is_ignored(&@ast::item i) -> bool { + attr::contains_name(attr::attr_metas(i.attrs), "ignore") +} + fn add_test_module(&test_ctxt cx, &ast::_mod m) -> ast::_mod { auto testmod = mk_test_module(cx); ret rec(items=m.items + ~[testmod] with m); @@ -225,10 +234,9 @@ fn mk_test_desc_vec(&test_ctxt cx) -> @ast::expr { log #fmt("building test vector from %u tests", ivec::len(cx.testfns)); auto descs = ~[]; - for (ast::ident[] testpath in cx.testfns) { - log #fmt("encoding %s", ast::path_name_i(testpath)); - auto path = testpath; - descs += ~[mk_test_desc_rec(cx, path)]; + for (test test in cx.testfns) { + auto test_ = test; // Satisfy alias analysis + descs += ~[mk_test_desc_rec(cx, test_)]; } ret @rec(id = cx.next_node_id(), @@ -236,7 +244,10 @@ fn mk_test_desc_vec(&test_ctxt cx) -> @ast::expr { span = rec(lo=0u,hi=0u)); } -fn mk_test_desc_rec(&test_ctxt cx, ast::ident[] path) -> @ast::expr { +fn mk_test_desc_rec(&test_ctxt cx, test test) -> @ast::expr { + auto path = test.path; + + log #fmt("encoding %s", ast::path_name_i(path)); let ast::lit name_lit = nospan(ast::lit_str(ast::path_name_i(path), ast::sk_rc)); @@ -260,7 +271,19 @@ fn mk_test_desc_rec(&test_ctxt cx, ast::ident[] path) -> @ast::expr { ident = "fn", expr = @fn_expr)); - let ast::expr_ desc_rec_ = ast::expr_rec(~[name_field, fn_field], + let ast::lit ignore_lit = nospan(ast::lit_bool(test.ignore)); + + let ast::expr ignore_expr = rec(id = cx.next_node_id(), + node = ast::expr_lit(@ignore_lit), + span = rec(lo=0u, hi=0u)); + + let ast::field ignore_field = nospan(rec(mut = ast::imm, + ident = "ignore", + expr = @ignore_expr)); + + let ast::expr_ desc_rec_ = ast::expr_rec(~[name_field, + fn_field, + ignore_field], option::none); let ast::expr desc_rec = rec(id = cx.next_node_id(), node = desc_rec_, diff --git a/src/lib/test.rs b/src/lib/test.rs index ebf82b14d03..037206b39f3 100644 --- a/src/lib/test.rs +++ b/src/lib/test.rs @@ -7,6 +7,11 @@ export test_name; export test_fn; export test_desc; export test_main; +export test_result; +export tr_ok; +export tr_failed; +export tr_ignored; +export run_test; // The name of a test. By convention this follows the rules for rust // paths, i.e it should be a series of identifiers seperated by double @@ -23,7 +28,8 @@ type test_fn = fn(); // The definition of a single test. A test runner will run a list of // these. type test_desc = rec(test_name name, - test_fn fn); + test_fn fn, + bool ignore); // The default console test runner. It accepts the command line // arguments and a vector of test_descs (generated at compile time). @@ -43,6 +49,12 @@ fn parse_opts(&vec[str] args) -> test_opts { }) } +tag test_result { + tr_ok; + tr_failed; + tr_ignored; +} + // A simple console test runner fn run_tests(&test_opts opts, &test_desc[] tests) -> bool { @@ -55,21 +67,30 @@ fn run_tests(&test_opts opts, &test_desc[] tests) -> bool { auto passed = 0u; auto failed = 0u; + auto ignored = 0u; for (test_desc test in filtered_tests) { out.write_str(#fmt("running %s ... ", test.name)); - if (run_test(test)) { - passed += 1u; - write_ok(out); - out.write_line(""); - } else { - failed += 1u; - write_failed(out); - out.write_line(""); + alt (run_test(test)) { + tr_ok { + passed += 1u; + write_ok(out); + out.write_line(""); + } + tr_failed { + failed += 1u; + write_failed(out); + out.write_line(""); + } + tr_ignored { + ignored += 1u; + write_ignored(out); + out.write_line(""); + } } } - assert passed + failed == total; + assert passed + failed + ignored == total; out.write_str(#fmt("\nresult: ")); if (failed == 0u) { @@ -77,16 +98,11 @@ fn run_tests(&test_opts opts, &test_desc[] tests) -> bool { } else { write_failed(out); } - out.write_str(#fmt(". %u passed; %u failed\n\n", - passed, failed)); + out.write_str(#fmt(". %u passed; %u failed; %u ignored\n\n", + passed, failed, ignored)); ret true; - fn run_test(&test_desc test) -> bool { - test.fn(); - ret true; - } - fn write_ok(&io::writer out) { if (term::color_supported()) { term::fg(out.get_buf_writer(), term::color_green); @@ -106,6 +122,16 @@ fn run_tests(&test_opts opts, &test_desc[] tests) -> bool { term::reset(out.get_buf_writer()); } } + + fn write_ignored(&io::writer out) { + if (term::color_supported()) { + term::fg(out.get_buf_writer(), term::color_yellow); + } + out.write_str("ignored"); + if (term::color_supported()) { + term::reset(out.get_buf_writer()); + } + } } fn filter_tests(&test_opts opts, &test_desc[] tests) -> test_desc[] { @@ -128,6 +154,15 @@ fn filter_tests(&test_opts opts, &test_desc[] tests) -> test_desc[] { ret ivec::filter_map(filter, tests); } +fn run_test(&test_desc test) -> test_result { + if (!test.ignore) { + test.fn(); + ret tr_ok; + } else { + ret tr_ignored; + } +} + // Local Variables: // mode: rust; diff --git a/src/test/stdtest/stdtest.rc b/src/test/stdtest/stdtest.rc index ab455001b61..5bce1659179 100644 --- a/src/test/stdtest/stdtest.rc +++ b/src/test/stdtest/stdtest.rc @@ -2,6 +2,7 @@ use std; mod sha1; mod int; +mod test; // Local Variables: // mode: rust // fill-column: 78; diff --git a/src/test/stdtest/test.rs b/src/test/stdtest/test.rs new file mode 100644 index 00000000000..17f18a5151e --- /dev/null +++ b/src/test/stdtest/test.rs @@ -0,0 +1,36 @@ +import std::test; + +#[test] +fn do_not_run_ignored_tests() { + auto ran = @mutable false; + auto f = bind fn(@mutable bool ran) { + *ran = true; + } (ran); + + auto desc = rec(name = "whatever", + fn = f, + ignore = true); + + auto res = test::run_test(desc); + + assert ran == false; +} + +#[test] +fn ignored_tests_result_in_ignored() { + fn f() { } + auto desc = rec(name = "whatever", + fn = f, + ignore = true); + auto res = test::run_test(desc); + assert res == test::tr_ignored; +} + +// Local Variables: +// mode: rust; +// fill-column: 78; +// indent-tabs-mode: nil +// c-basic-offset: 4 +// buffer-file-coding-system: utf-8-unix +// compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'"; +// End: