From ac2424dd22eb5faf5da86cef6d723d07e548a7b4 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 25 Jul 2011 15:21:36 -0700 Subject: [PATCH] Run test tasks in parallel according to RUST_THREADS. Issue #734 --- src/lib/test.rs | 109 ++++++++++++++++++++++++++++----------- src/test/stdtest/test.rs | 2 +- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/lib/test.rs b/src/lib/test.rs index 37a2a2cb3e29..d46c84eda79f 100644 --- a/src/lib/test.rs +++ b/src/lib/test.rs @@ -4,6 +4,7 @@ // while providing a base that other test frameworks may build off of. import sort = sort::ivector; +import getenv = generic_os::getenv; export test_name; export test_fn; @@ -104,32 +105,52 @@ fn run_tests_console(&test_opts opts, &test_desc[] tests) -> bool { auto total = ivec::len(filtered_tests); out.write_line(#fmt("running %u tests", total)); + auto futures = ~[]; + auto passed = 0u; auto failed = 0u; auto ignored = 0u; auto failures = ~[]; - for (test_desc test in filtered_tests) { - out.write_str(#fmt("running %s ... ", test.name)); - alt (run_test(test)) { + // It's tempting to just spawn all the tests at once but that doesn't + // provide a great user experience because you might sit waiting for the + // result of a particular test for an unusually long amount of time. + auto concurrency = get_concurrency(); + log #fmt("using %u test tasks", concurrency); + auto run_idx = 0u; + auto wait_idx = 0u; + + while (wait_idx < total) { + while (ivec::len(futures) < concurrency + && run_idx < total) { + futures += ~[run_test(filtered_tests.(run_idx))]; + run_idx += 1u; + } + + auto future = futures.(0); + out.write_str(#fmt("running %s ... ", future.test.name)); + auto result = future.wait(); + alt (result) { tr_ok { passed += 1u; - write_ok(out); + write_ok(out, concurrency); out.write_line(""); } tr_failed { failed += 1u; - write_failed(out); + write_failed(out, concurrency); out.write_line(""); - failures += ~[test]; + failures += ~[future.test]; } tr_ignored { ignored += 1u; - write_ignored(out); + write_ignored(out, concurrency); out.write_line(""); } } + futures = ivec::slice(futures, 1u, ivec::len(futures)); + wait_idx += 1u; } assert passed + failed + ignored == total; @@ -144,38 +165,53 @@ fn run_tests_console(&test_opts opts, &test_desc[] tests) -> bool { out.write_str(#fmt("\nresult: ")); if (success) { - write_ok(out); + write_ok(out, concurrency); } else { - write_failed(out); + write_failed(out, concurrency); } out.write_str(#fmt(". %u passed; %u failed; %u ignored\n\n", passed, failed, ignored)); ret success; - fn write_ok(&io::writer out) { - write_pretty(out, "ok", term::color_green); + fn write_ok(&io::writer out, uint concurrency) { + write_pretty(out, "ok", term::color_green, concurrency); } - fn write_failed(&io::writer out) { - write_pretty(out, "FAILED", term::color_red); + fn write_failed(&io::writer out, uint concurrency) { + write_pretty(out, "FAILED", term::color_red, concurrency); } - fn write_ignored(&io::writer out) { - write_pretty(out, "ignored", term::color_yellow); + fn write_ignored(&io::writer out, uint concurrency) { + write_pretty(out, "ignored", term::color_yellow, concurrency); } - fn write_pretty(&io::writer out, &str word, u8 color) { - if (term::color_supported()) { + fn write_pretty(&io::writer out, &str word, u8 color, + uint concurrency) { + // In the presence of concurrency, outputing control characters + // can cause some crazy artifacting + if (concurrency == 1u && term::color_supported()) { term::fg(out.get_buf_writer(), color); } out.write_str(word); - if (term::color_supported()) { + if (concurrency == 1u && term::color_supported()) { term::reset(out.get_buf_writer()); } } } +fn get_concurrency() -> uint { + alt getenv("RUST_THREADS") { + option::some(?t) { + auto threads = uint::parse_buf(str::bytes(t), 10u); + threads > 0u ? threads : 1u + } + option::none { + 1u + } + } +} + fn filter_tests(&test_opts opts, &test_desc[] tests) -> test_desc[] { auto filtered = tests; @@ -226,15 +262,30 @@ fn filter_tests(&test_opts opts, &test_desc[] tests) -> test_desc[] { ret filtered; } -fn run_test(&test_desc test) -> test_result { +type test_future = rec(test_desc test, + @fn() fnref, + fn() -> test_result wait); + +fn run_test(&test_desc test) -> test_future { + // FIXME: Because of the unsafe way we're passing the test function + // to the test task, we need to make sure we keep a reference to that + // function around for longer than the lifetime of the task. To that end + // we keep the function boxed in the test future. + auto fnref = @test.fn; if (!test.ignore) { - if (run_test_fn_in_task(test.fn)) { - ret tr_ok; - } else { - ret tr_failed; - } + auto test_task = run_test_fn_in_task(*fnref); + ret rec(test = test, + fnref = fnref, + wait = bind fn(&task test_task) -> test_result { + alt (task::join(test_task)) { + task::tr_success { tr_ok } + task::tr_failure { tr_failed } + } + } (test_task)); } else { - ret tr_ignored; + ret rec(test = test, + fnref = fnref, + wait = fn() -> test_result { tr_ignored }); } } @@ -245,7 +296,7 @@ native "rust" mod rustrt { // We need to run our tests in another task in order to trap test failures. // But, at least currently, functions can't be used as spawn arguments so // we've got to treat our test functions as unsafe pointers. -fn run_test_fn_in_task(&fn() f) -> bool { +fn run_test_fn_in_task(&fn() f) -> task { fn run_task(*mutable fn() fptr) { // If this task fails we don't want that failure to propagate to the // test runner or else we couldn't keep running tests @@ -261,11 +312,7 @@ fn run_test_fn_in_task(&fn() f) -> bool { (*fptr)() } auto fptr = ptr::addr_of(f); - auto test_task = spawn run_task(fptr); - ret alt (task::join(test_task)) { - task::tr_success { true } - task::tr_failure { false } - } + ret spawn run_task(fptr); } diff --git a/src/test/stdtest/test.rs b/src/test/stdtest/test.rs index 7bf5b3cc9317..e4317b10727e 100644 --- a/src/test/stdtest/test.rs +++ b/src/test/stdtest/test.rs @@ -26,7 +26,7 @@ fn ignored_tests_result_in_ignored() { auto desc = rec(name = "whatever", fn = f, ignore = true); - auto res = test::run_test(desc); + auto res = test::run_test(desc).wait(); assert res == test::tr_ignored; }