From b306a0d714215717c7cc2fbabd7fd74438fe77e5 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 28 Jul 2011 23:24:07 -0700 Subject: [PATCH] Get compile tests to run in parallel Takes a lot of workarounds. The biggest problem is that boxes still don't seem to be moved across channels and bad things happen when the receiver destroys them. So there's all sorts of defensive cloning and scoping going on here to make the box lifetimes come out right. --- src/test/compiletest/compiletest.rs | 117 ++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 31 deletions(-) diff --git a/src/test/compiletest/compiletest.rs b/src/test/compiletest/compiletest.rs index 1f5e49134148..0c1f85a4d85d 100644 --- a/src/test/compiletest/compiletest.rs +++ b/src/test/compiletest/compiletest.rs @@ -11,6 +11,7 @@ import std::generic_os::getenv; import std::os; import std::run; import std::task; +import std::unsafe; tag mode { mode_compile_fail; mode_run_fail; mode_run_pass; } @@ -175,7 +176,6 @@ fn make_test(cx: &cx, testfile: &str, configport: &port[str]) -> ignore: header::is_test_ignored(cx.config, testfile)} } - /* So this is kind of crappy: @@ -223,7 +223,7 @@ fn closure_to_task(cx: cx, configport: port[str], testfn: &fn() ) -> task { cx.config.run_ignored, opt_str(cx.config.filter), opt_str(cx.config.runtool), opt_str(cx.config.rustcflags), cx.config.verbose, - procsrv::clone(cx.procsrv).chan, testfile); + task::clone_chan(cx.procsrv.chan), testfile); } fn run_test_task(compile_lib_path: str, run_lib_path: str, rustc_path: str, @@ -304,6 +304,23 @@ fn logv(config: &config, s: &str) { if config.verbose { io::stdout().write_line(s); } } +fn clone_str(s: &str) -> str { + let new = s + ""; + // new should be a different pointer + let sptr: int = unsafe::reinterpret_cast(s); + let newptr: int = unsafe::reinterpret_cast(new); + assert sptr != newptr; + new +} + +fn clone_ivecstr(v: &str[]) -> str[] { + let r = ~[]; + for t: str in ivec::slice(v, 0u, ivec::len(v)) { + r += ~[clone_str(t)]; + } + ret r; +} + mod header { export test_props; @@ -396,10 +413,10 @@ mod runtest { log #fmt("running %s", testfile); let props = load_props(testfile); alt cx.config.mode { - mode_compile_fail. { run_cfail_test(cx, props, testfile); } - mode_run_fail. { run_rfail_test(cx, props, testfile); } - mode_run_pass. { run_rpass_test(cx, props, testfile); } - } + mode_compile_fail. { run_cfail_test(cx, props, testfile); } + mode_run_fail. { run_rfail_test(cx, props, testfile); } + mode_run_pass. { run_rpass_test(cx, props, testfile); } + } } fn run_cfail_test(cx: &cx, props: &test_props, testfile: &str) { @@ -622,7 +639,7 @@ mod procsrv { type handle = {task: option::t[task], chan: reqchan}; - tag request { exec(str, str, vec[str], chan[response]); stop; } + tag request { exec(str, str, str[], chan[response]); stop; } type response = {pid: int, outfd: int, errfd: int}; @@ -631,7 +648,7 @@ mod procsrv { let task = spawn fn(setupchan: chan[chan[request]]) { let reqport = port(); let reqchan = chan(reqport); - task::send(setupchan, reqchan); + task::send(setupchan, task::clone_chan(reqchan)); worker(reqport); } (chan(setupport)); ret {task: option::some(task), @@ -657,7 +674,10 @@ mod procsrv { {status: int, out: str, err: str} { let p = port[response](); let ch = chan(p); - task::send(handle.chan, exec(lib_path, prog, args, ch)); + task::send(handle.chan, exec(lib_path, + prog, + clone_ivecstr(ivec::from_vec(args)), + task::clone_chan(ch))); let resp = task::recv(p); let output = readclose(resp.outfd); let errput = readclose(resp.errfd); @@ -679,29 +699,64 @@ mod procsrv { } fn worker(p: port[request]) { + + // FIXME: If we declare this inside of the while loop and then + // break out of it before it's ever initialized (i.e. we don't run + // any tests), then the cleanups will puke, so we're initializing it + // here with defaults. + let execparms = { + lib_path: "", + prog: "", + args: ~[], + // This works because a NULL box is ignored during cleanup + respchan: unsafe::reinterpret_cast(0) + }; + while true { - alt task::recv(p) { - exec(lib_path, prog, args, respchan) { - // This is copied from run::start_program - let pipe_in = os::pipe(); - let pipe_out = os::pipe(); - let pipe_err = os::pipe(); - let spawnproc = - bind run::spawn_process(prog, args, pipe_in.in, - pipe_out.out, pipe_err.out); - let pid = with_lib_path(lib_path, spawnproc); - if pid == -1 { fail; } - os::libc::close(pipe_in.in); - os::libc::close(pipe_in.out); - os::libc::close(pipe_out.out); - os::libc::close(pipe_err.out); - task::send(respchan, - {pid: pid, - outfd: pipe_out.in, - errfd: pipe_err.in}); - } - stop. { ret; } - } + // FIXME: Sending strings across channels seems to still + // leave them refed on the sender's end, which causes problems if + // the receiver's poniters outlive the sender's. Here we clone + // everything and let the originals go out of scope before sending + // a response. + execparms = { + // FIXME: The 'discriminant' of an alt expression has the + // same scope as the alt expression itself, so we have to put + // the entire alt in another block to make sure the exec + // message goes out of scope. Seems like the scoping rules for + // the alt discriminant are wrong. + alt task::recv(p) { + exec(lib_path, prog, args, respchan) { + { + lib_path: clone_str(lib_path), + prog: clone_str(prog), + args: clone_ivecstr(args), + respchan: respchan + } + } + stop. { ret } + } + }; + + // This is copied from run::start_program + let pipe_in = os::pipe(); + let pipe_out = os::pipe(); + let pipe_err = os::pipe(); + let spawnproc = + bind run::spawn_process(execparms.prog, + ivec::to_vec(execparms.args), + pipe_in.in, + pipe_out.out, + pipe_err.out); + let pid = with_lib_path(execparms.lib_path, spawnproc); + if pid == -1 { fail; } + os::libc::close(pipe_in.in); + os::libc::close(pipe_in.out); + os::libc::close(pipe_out.out); + os::libc::close(pipe_err.out); + task::send(execparms.respchan, + {pid: pid, + outfd: pipe_out.in, + errfd: pipe_err.in}); } }