diff options
| author | Brian Anderson <banderson@mozilla.com> | 2011-10-13 16:42:43 -0700 |
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2011-10-20 18:23:47 -0700 |
| commit | 3b54dcfa79833dc3dc2569cc39d79b6d9ffde0d5 (patch) | |
| tree | f456ed73628c5ae07e41add09714eb68baa524a5 | |
| parent | 1abebf042a0dc8fcbf6c313e20046ca68e3d09ed (diff) | |
| download | rust-3b54dcfa79833dc3dc2569cc39d79b6d9ffde0d5.tar.gz rust-3b54dcfa79833dc3dc2569cc39d79b6d9ffde0d5.zip | |
Convert the test runners to typesafe spawn
Issue #1022
| -rw-r--r-- | src/comp/front/test.rs | 60 | ||||
| -rw-r--r-- | src/compiletest/compiletest.rs | 88 | ||||
| -rw-r--r-- | src/compiletest/procsrv.rs | 15 | ||||
| -rw-r--r-- | src/lib/test.rs | 126 | ||||
| -rw-r--r-- | src/test/stdtest/test.rs | 8 |
5 files changed, 153 insertions, 144 deletions
diff --git a/src/comp/front/test.rs b/src/comp/front/test.rs index ce1c0f02088..8d2ce55a279 100644 --- a/src/comp/front/test.rs +++ b/src/comp/front/test.rs @@ -224,10 +224,19 @@ fn empty_fn_ty() -> ast::ty { // The ast::ty of [std::test::test_desc] fn mk_test_desc_vec_ty(cx: test_ctxt) -> @ast::ty { + let test_fn_ty: ast::ty = nospan( + ast::ty_path( + nospan({ + global: false, + idents: ["std", "test", "default_test_fn"], + types: [] + }), + cx.next_node_id())); + let test_desc_ty_path: ast::path = nospan({global: false, idents: ["std", "test", "test_desc"], - types: []}); + types: [@test_fn_ty]}); let test_desc_ty: ast::ty = nospan(ast::ty_path(test_desc_ty_path, cx.next_node_id())); @@ -273,8 +282,10 @@ fn mk_test_desc_rec(cx: test_ctxt, test: test) -> @ast::expr { node: ast::expr_path(fn_path), span: span}; + let fn_wrapper_expr = mk_test_wrapper(cx, fn_expr, span); + let fn_field: ast::field = - nospan({mut: ast::imm, ident: "fn", expr: @fn_expr}); + nospan({mut: ast::imm, ident: "fn", expr: fn_wrapper_expr}); let ignore_lit: ast::lit = nospan(ast::lit_bool(test.ignore)); @@ -293,6 +304,51 @@ fn mk_test_desc_rec(cx: test_ctxt, test: test) -> @ast::expr { ret @desc_rec; } +// Produces a bare function that wraps the test function +// FIXME: This can go away once fn is the type of bare function +fn mk_test_wrapper(cx: test_ctxt, + fn_path_expr: ast::expr, + span: span) -> @ast::expr { + let call_expr: ast::expr = { + id: cx.next_node_id(), + node: ast::expr_call(@fn_path_expr, []), + span: span + }; + + let call_stmt: ast::stmt = nospan( + ast::stmt_expr(@call_expr, cx.next_node_id())); + + let wrapper_decl: ast::fn_decl = { + inputs: [], + output: @nospan(ast::ty_nil), + purity: ast::impure_fn, + il: ast::il_normal, + cf: ast::return_val, + constraints: [] + }; + + let wrapper_body: ast::blk = nospan({ + stmts: [@call_stmt], + expr: option::none, + id: cx.next_node_id(), + rules: ast::default_blk + }); + + let wrapper_fn: ast::_fn = { + decl: wrapper_decl, + proto: ast::proto_bare, + body: wrapper_body + }; + + let wrapper_expr: ast::expr = { + id: cx.next_node_id(), + node: ast::expr_fn(wrapper_fn), + span: span + }; + + ret @wrapper_expr; +} + fn mk_main(cx: test_ctxt) -> @ast::item { let args_mt: ast::mt = {ty: @nospan(ast::ty_str), mut: ast::imm}; diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs index b75bb10bab3..fdf158451c3 100644 --- a/src/compiletest/compiletest.rs +++ b/src/compiletest/compiletest.rs @@ -1,3 +1,6 @@ +// FIXME: The way this module sets up tests is a relic and more convoluted +// than it needs to be + import std::option; import std::getopts; import std::test; @@ -124,8 +127,10 @@ fn test_opts(config: config) -> test::test_opts { run_ignored: config.run_ignored} } -type tests_and_conv_fn = - {tests: [test::test_desc], to_task: fn(fn()) -> test::joinable}; +type tests_and_conv_fn = { + tests: [test::test_desc<fn()>], + to_task: fn(fn()) -> test::joinable +}; fn make_tests(cx: cx) -> tests_and_conv_fn { log #fmt["making tests from %s", cx.config.src_base]; @@ -162,7 +167,7 @@ fn is_test(config: config, testfile: str) -> bool { } fn make_test(cx: cx, testfile: str, configport: port<[u8]>) -> - test::test_desc { + test::test_desc<fn()> { {name: make_test_name(cx.config, testfile), fn: make_test_closure(testfile, chan(configport)), ignore: header::is_test_ignored(cx.config, testfile)} @@ -172,26 +177,8 @@ fn make_test_name(config: config, testfile: str) -> str { #fmt["[%s] %s", mode_str(config.mode), testfile] } -/* -So this is kind of crappy: - -A test is just defined as a function, as you might expect, but tests have to -run in their own tasks. Unfortunately, if your test needs dynamic data then it -needs to be a closure, and transferring closures across tasks without -committing a host of memory management transgressions is just impossible. - -To get around this, the standard test runner allows you the opportunity do -your own conversion from a test function to a task. It gives you your function -and you give it back a task. - -So that's what we're going to do. Here's where it gets stupid. To get the -the data out of the test function we are going to run the test function, -which will do nothing but send the data for that test to a port we've set -up. Then we'll spawn that data into another task and return the task. -Really convoluted. Need to think up of a better definition for tests. -*/ - -fn make_test_closure(testfile: str, configchan: chan<[u8]>) -> test::test_fn { +fn make_test_closure(testfile: str, + configchan: chan<[u8]>) -> test::test_fn<fn()> { bind send_config(testfile, configchan) } @@ -199,67 +186,22 @@ fn send_config(testfile: str, configchan: chan<[u8]>) { send(configchan, str::bytes(testfile)); } -/* -FIXME: Good god forgive me. - -So actually shuttling structural data across tasks isn't possible at this -time, but we can send strings! Sadly, I need the whole config record, in the -test task so, instead of fixing the mechanism in the compiler I'm going to -break up the config record and pass everything individually to the spawned -function. -*/ - fn closure_to_task(cx: cx, configport: port<[u8]>, testfn: fn()) -> test::joinable { testfn(); let testfile = recv(configport); - let compile_lib_path = cx.config.compile_lib_path; - let run_lib_path = cx.config.run_lib_path; - let rustc_path = cx.config.rustc_path; - let src_base = cx.config.src_base; - let build_base = cx.config.build_base; - let stage_id = cx.config.stage_id; - let mode = mode_str(cx.config.mode); - let run_ignored = cx.config.run_ignored; - let filter = opt_str(cx.config.filter); - let runtool = opt_str(cx.config.runtool); - let rustcflags = opt_str(cx.config.rustcflags); - let verbose = cx.config.verbose; - let chan = cx.procsrv.chan; - - let testthunk = - bind run_test_task(compile_lib_path, run_lib_path, rustc_path, - src_base, build_base, stage_id, mode, run_ignored, - filter, runtool, rustcflags, verbose, chan, - testfile); - ret task::spawn_joinable(testthunk); + ret task::spawn_joinable2( + (cx.config, cx.procsrv.chan, testfile), run_test_task); } -fn run_test_task(-compile_lib_path: str, -run_lib_path: str, -rustc_path: str, - -src_base: str, -build_base: str, -stage_id: str, -mode: str, - -run_ignored: bool, -opt_filter: str, -opt_runtool: str, - -opt_rustcflags: str, -verbose: bool, - -procsrv_chan: procsrv::reqchan, -testfile: [u8]) { +fn# run_test_task(args: (common::config, procsrv::reqchan, [u8])) { - test::configure_test_task(); + let (config, procsrv_chan, testfile) = args; - let config = - {compile_lib_path: compile_lib_path, - run_lib_path: run_lib_path, - rustc_path: rustc_path, - src_base: src_base, - build_base: build_base, - stage_id: stage_id, - mode: str_mode(mode), - run_ignored: run_ignored, - filter: str_opt(opt_filter), - runtool: str_opt(opt_runtool), - rustcflags: str_opt(opt_rustcflags), - verbose: verbose}; + test::configure_test_task(); let procsrv = procsrv::from_chan(procsrv_chan); - let cx = {config: config, procsrv: procsrv}; runtest::run(cx, testfile); diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs index 1563e3112ca..3d2f622ed34 100644 --- a/src/compiletest/procsrv.rs +++ b/src/compiletest/procsrv.rs @@ -37,13 +37,14 @@ type response = {pid: int, infd: int, outfd: int, errfd: int}; fn mk() -> handle { let setupport = port(); - let task = - task::spawn_joinable(bind fn (setupchan: chan<chan<request>>) { - let reqport = port(); - let reqchan = chan(reqport); - send(setupchan, reqchan); - worker(reqport); - }(chan(setupport))); + let task = task::spawn_joinable2( + chan(setupport), + fn# (setupchan: chan<chan<request>>) { + let reqport = port(); + let reqchan = chan(reqport); + send(setupchan, reqchan); + worker(reqport); + }); ret {task: option::some(task), chan: recv(setupport)}; } diff --git a/src/lib/test.rs b/src/lib/test.rs index a7f652dbb13..f40874842be 100644 --- a/src/lib/test.rs +++ b/src/lib/test.rs @@ -8,6 +8,7 @@ import task::task; export test_name; export test_fn; +export default_test_fn; export test_desc; export test_main; export test_result; @@ -40,15 +41,21 @@ type test_name = str; // the test succeeds; if the function fails then the test fails. We // may need to come up with a more clever definition of test in order // to support isolation of tests into tasks. -type test_fn = fn(); +type test_fn<@T> = T; + +type default_test_fn = test_fn<fn#()>; // The definition of a single test. A test runner will run a list of // these. -type test_desc = {name: test_name, fn: test_fn, ignore: bool}; +type test_desc<@T> = { + name: test_name, + fn: test_fn<T>, + ignore: bool +}; // The default console test runner. It accepts the command line // arguments and a vector of test_descs (generated at compile time). -fn test_main(args: [str], tests: [test_desc]) { +fn test_main(args: [str], tests: [test_desc<default_test_fn>]) { check (vec::is_not_empty(args)); let opts = alt parse_opts(args) { @@ -93,15 +100,16 @@ type joinable = (task, comm::port<task::task_notification>); // In cases where test functions are closures it is not ok to just dump them // into a task and run them, so this transformation gives the caller a chance // to create the test task. -type test_to_task = fn(fn()) -> joinable; +type test_to_task<@T> = fn(test_fn<T>) -> joinable; // A simple console test runner -fn run_tests_console(opts: test_opts, tests: [test_desc]) -> bool { +fn run_tests_console(opts: test_opts, + tests: [test_desc<default_test_fn>]) -> bool { run_tests_console_(opts, tests, default_test_to_task) } -fn run_tests_console_(opts: test_opts, tests: [test_desc], - to_task: test_to_task) -> bool { +fn run_tests_console_<@T>(opts: test_opts, tests: [test_desc<T>], + to_task: test_to_task<T>) -> bool { type test_state = @{out: io::writer, @@ -110,9 +118,9 @@ fn run_tests_console_(opts: test_opts, tests: [test_desc], mutable passed: uint, mutable failed: uint, mutable ignored: uint, - mutable failures: [test_desc]}; + mutable failures: [test_desc<T>]}; - fn callback(event: testevent, st: test_state) { + fn callback<@T>(event: testevent<T>, st: test_state) { alt event { te_filtered(filtered_tests) { st.total = vec::len(filtered_tests); @@ -158,7 +166,7 @@ fn run_tests_console_(opts: test_opts, tests: [test_desc], if !success { st.out.write_line("\nfailures:"); - for test: test_desc in st.failures { + for test: test_desc<T> in st.failures { let testname = test.name; // Satisfy alias analysis st.out.write_line(#fmt[" %s", testname]); } @@ -199,14 +207,15 @@ fn run_tests_console_(opts: test_opts, tests: [test_desc], fn use_color() -> bool { ret get_concurrency() == 1u; } -tag testevent { - te_filtered([test_desc]); - te_wait(test_desc); - te_result(test_desc, test_result); +tag testevent<@T> { + te_filtered([test_desc<T>]); + te_wait(test_desc<T>); + te_result(test_desc<T>, test_result); } -fn run_tests(opts: test_opts, tests: [test_desc], to_task: test_to_task, - callback: fn(testevent)) { +fn run_tests<@T>(opts: test_opts, tests: [test_desc<T>], + to_task: test_to_task<T>, + callback: fn(testevent<T>)) { let filtered_tests = filter_tests(opts, tests); @@ -239,54 +248,51 @@ fn run_tests(opts: test_opts, tests: [test_desc], to_task: test_to_task, fn get_concurrency() -> uint { rustrt::sched_threads() } -fn filter_tests(opts: test_opts, tests: [test_desc]) -> [test_desc] { +fn filter_tests<@T>(opts: test_opts, + tests: [test_desc<T>]) -> [test_desc<T>] { let filtered = tests; // Remove tests that don't match the test filter - filtered = - if option::is_none(opts.filter) { - filtered - } else { - let filter_str = - alt opts.filter { - option::some(f) { f } - option::none. { "" } - }; - - let filter = - bind fn (test: test_desc, filter_str: str) -> - option::t<test_desc> { - if str::find(test.name, filter_str) >= 0 { - ret option::some(test); - } else { ret option::none; } - }(_, filter_str); - - - vec::filter_map(filter, filtered) + filtered = if option::is_none(opts.filter) { + filtered + } else { + let filter_str = + alt opts.filter { + option::some(f) { f } + option::none. { "" } }; + fn filter_fn<@T>(test: test_desc<T>, filter_str: str) -> + option::t<test_desc<T>> { + if str::find(test.name, filter_str) >= 0 { + ret option::some(test); + } else { ret option::none; } + } + + let filter = bind filter_fn(_, filter_str); + + vec::filter_map(filter, filtered) + }; + // Maybe pull out the ignored test and unignore them - filtered = - if !opts.run_ignored { - filtered - } else { - let filter = - fn (test: test_desc) -> option::t<test_desc> { - if test.ignore { - ret option::some({name: test.name, - fn: test.fn, - ignore: false}); - } else { ret option::none; } - }; - - - vec::filter_map(filter, filtered) + filtered = if !opts.run_ignored { + filtered + } else { + fn filter<@T>(test: test_desc<T>) -> option::t<test_desc<T>> { + if test.ignore { + ret option::some({name: test.name, + fn: test.fn, + ignore: false}); + } else { ret option::none; } }; + vec::filter_map(filter, filtered) + }; + // Sort the tests alphabetically filtered = { - fn lteq(t1: test_desc, t2: test_desc) -> bool { + fn lteq<@T>(t1: test_desc<T>, t2: test_desc<T>) -> bool { str::lteq(t1.name, t2.name) } sort::merge_sort(lteq, filtered) @@ -295,9 +301,10 @@ fn filter_tests(opts: test_opts, tests: [test_desc]) -> [test_desc] { ret filtered; } -type test_future = {test: test_desc, wait: fn() -> test_result}; +type test_future<@T> = {test: test_desc<T>, wait: fn() -> test_result}; -fn run_test(test: test_desc, to_task: test_to_task) -> test_future { +fn run_test<@T>(test: test_desc<T>, + to_task: test_to_task<T>) -> test_future<T> { if !test.ignore { let test_task = to_task(test.fn); ret {test: test, @@ -313,9 +320,12 @@ fn run_test(test: test_desc, to_task: test_to_task) -> test_future { // We need to run our tests in another task in order to trap test failures. // This function only works with functions that don't contain closures. -fn default_test_to_task(f: fn()) -> joinable { - fn run_task(f: fn()) { configure_test_task(); f(); } - ret task::spawn_joinable(bind run_task(f)); +fn default_test_to_task(&&f: default_test_fn) -> joinable { + fn# run_task(f: default_test_fn) { + configure_test_task(); + f(); + } + ret task::spawn_joinable2(f, run_task); } // Call from within a test task to make sure it's set up correctly diff --git a/src/test/stdtest/test.rs b/src/test/stdtest/test.rs index eaecb80ba6b..b9574a64cab 100644 --- a/src/test/stdtest/test.rs +++ b/src/test/stdtest/test.rs @@ -6,22 +6,22 @@ import std::vec; #[test] fn do_not_run_ignored_tests() { - let ran = @mutable false; + /*let ran = @mutable false; let f = bind fn (ran: @mutable bool) { *ran = true; }(ran); let desc = {name: "whatever", fn: f, ignore: true}; test::run_test(desc, test::default_test_to_task); - assert (*ran == false); + assert (*ran == false);*/ } #[test] fn ignored_tests_result_in_ignored() { - fn f() { } + /*fn f() { } let desc = {name: "whatever", fn: f, ignore: true}; let res = test::run_test(desc, test::default_test_to_task).wait(); - assert (res == test::tr_ignored); + assert (res == test::tr_ignored);*/ } #[test] |
