diff options
| author | Brian Anderson <banderson@mozilla.com> | 2011-10-02 17:28:59 -0700 |
|---|---|---|
| committer | Brian Anderson <banderson@mozilla.com> | 2011-10-02 17:28:59 -0700 |
| commit | 3a6f3cf275061c03c8169ce50779efbda0faa845 (patch) | |
| tree | 04496acc42c514c463c01f34684a575703145134 /src/compiletest | |
| parent | edb56507eee87e44625813a94a06b8e28dfb617e (diff) | |
| download | rust-3a6f3cf275061c03c8169ce50779efbda0faa845.tar.gz rust-3a6f3cf275061c03c8169ce50779efbda0faa845.zip | |
Move compiletest to src/ and cleanup build rules
Diffstat (limited to 'src/compiletest')
| -rw-r--r-- | src/compiletest/common.rs | 32 | ||||
| -rw-r--r-- | src/compiletest/compiletest.rc | 16 | ||||
| -rw-r--r-- | src/compiletest/compiletest.rs | 274 | ||||
| -rw-r--r-- | src/compiletest/header.rs | 117 | ||||
| -rw-r--r-- | src/compiletest/procsrv.rs | 183 | ||||
| -rw-r--r-- | src/compiletest/runtest.rs | 392 | ||||
| -rw-r--r-- | src/compiletest/util.rs | 39 |
7 files changed, 1053 insertions, 0 deletions
diff --git a/src/compiletest/common.rs b/src/compiletest/common.rs new file mode 100644 index 00000000000..ab0415fd7c1 --- /dev/null +++ b/src/compiletest/common.rs @@ -0,0 +1,32 @@ +import std::option; + +tag mode { mode_compile_fail; mode_run_fail; mode_run_pass; mode_pretty; } + +type config = + // The library paths required for running the compiler + // The library paths required for running compiled programs + // The rustc executable + // The directory containing the tests to run + // The directory where programs should be built + // The name of the stage being built (stage1, etc) + // The test mode, compile-fail, run-fail, run-pass + // Run ignored tests + // Only run tests that match this filter + // A command line to prefix program execution with, + // for running under valgrind + // Flags to pass to the compiler + // Explain what's going on + {compile_lib_path: str, + run_lib_path: str, + rustc_path: str, + src_base: str, + build_base: str, + stage_id: str, + mode: mode, + run_ignored: bool, + filter: option::t<str>, + runtool: option::t<str>, + rustcflags: option::t<str>, + verbose: bool}; + +type cx = {config: config, procsrv: procsrv::handle}; diff --git a/src/compiletest/compiletest.rc b/src/compiletest/compiletest.rc new file mode 100644 index 00000000000..2f9ffe50f81 --- /dev/null +++ b/src/compiletest/compiletest.rc @@ -0,0 +1,16 @@ +use std; + +mod compiletest; +mod procsrv; +mod util; +mod header; +mod runtest; +mod common; + +// Local Variables: +// fill-column: 78; +// indent-tabs-mode: nil +// c-basic-offset: 4 +// buffer-file-coding-system: utf-8-unix +// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'"; +// End: diff --git a/src/compiletest/compiletest.rs b/src/compiletest/compiletest.rs new file mode 100644 index 00000000000..b75bb10bab3 --- /dev/null +++ b/src/compiletest/compiletest.rs @@ -0,0 +1,274 @@ +import std::option; +import std::getopts; +import std::test; +import std::fs; +import std::str; +import std::vec; +import std::task; + +import std::comm; +import std::comm::port; +import std::comm::chan; +import std::comm::send; +import std::comm::recv; + +import common::cx; +import common::config; +import common::mode_run_pass; +import common::mode_run_fail; +import common::mode_compile_fail; +import common::mode_pretty; +import common::mode; +import util::logv; + +fn main(args: [str]) { + let config = parse_config(args); + log_config(config); + run_tests(config); +} + +fn parse_config(args: [str]) -> config { + let opts = + [getopts::reqopt("compile-lib-path"), getopts::reqopt("run-lib-path"), + getopts::reqopt("rustc-path"), getopts::reqopt("src-base"), + getopts::reqopt("build-base"), getopts::reqopt("stage-id"), + getopts::reqopt("mode"), getopts::optflag("ignored"), + getopts::optopt("runtool"), getopts::optopt("rustcflags"), + getopts::optflag("verbose")]; + + check (vec::is_not_empty(args)); + let args_ = vec::tail(args); + let match = + alt getopts::getopts(args_, opts) { + getopts::success(m) { m } + getopts::failure(f) { fail getopts::fail_str(f) } + }; + + ret {compile_lib_path: getopts::opt_str(match, "compile-lib-path"), + run_lib_path: getopts::opt_str(match, "run-lib-path"), + rustc_path: getopts::opt_str(match, "rustc-path"), + src_base: getopts::opt_str(match, "src-base"), + build_base: getopts::opt_str(match, "build-base"), + stage_id: getopts::opt_str(match, "stage-id"), + mode: str_mode(getopts::opt_str(match, "mode")), + run_ignored: getopts::opt_present(match, "ignored"), + filter: + if vec::len(match.free) > 0u { + option::some(match.free[0]) + } else { option::none }, + runtool: getopts::opt_maybe_str(match, "runtool"), + rustcflags: getopts::opt_maybe_str(match, "rustcflags"), + verbose: getopts::opt_present(match, "verbose")}; +} + +fn log_config(config: config) { + let c = config; + logv(c, #fmt["configuration:"]); + logv(c, #fmt["compile_lib_path: %s", config.compile_lib_path]); + logv(c, #fmt["run_lib_path: %s", config.run_lib_path]); + logv(c, #fmt["rustc_path: %s", config.rustc_path]); + logv(c, #fmt["src_base: %s", config.src_base]); + logv(c, #fmt["build_base: %s", config.build_base]); + logv(c, #fmt["stage_id: %s", config.stage_id]); + logv(c, #fmt["mode: %s", mode_str(config.mode)]); + logv(c, #fmt["run_ignored: %b", config.run_ignored]); + logv(c, #fmt["filter: %s", opt_str(config.filter)]); + logv(c, #fmt["runtool: %s", opt_str(config.runtool)]); + logv(c, #fmt["rustcflags: %s", opt_str(config.rustcflags)]); + logv(c, #fmt["verbose: %b", config.verbose]); + logv(c, #fmt["\n"]); +} + +fn opt_str(maybestr: option::t<str>) -> str { + alt maybestr { option::some(s) { s } option::none. { "(none)" } } +} + +fn str_opt(maybestr: str) -> option::t<str> { + if maybestr != "(none)" { option::some(maybestr) } else { option::none } +} + +fn str_mode(s: str) -> mode { + alt s { + "compile-fail" { mode_compile_fail } + "run-fail" { mode_run_fail } + "run-pass" { mode_run_pass } + "pretty" { mode_pretty } + _ { fail "invalid mode" } + } +} + +fn mode_str(mode: mode) -> str { + alt mode { + mode_compile_fail. { "compile-fail" } + mode_run_fail. { "run-fail" } + mode_run_pass. { "run-pass" } + mode_pretty. { "pretty" } + } +} + +fn run_tests(config: config) { + let opts = test_opts(config); + let cx = {config: config, procsrv: procsrv::mk()}; + let tests = make_tests(cx); + let res = test::run_tests_console_(opts, tests.tests, tests.to_task); + procsrv::close(cx.procsrv); + if !res { fail "Some tests failed"; } +} + +fn test_opts(config: config) -> test::test_opts { + {filter: + alt config.filter { + option::some(s) { option::some(s) } + option::none. { option::none } + }, + run_ignored: config.run_ignored} +} + +type tests_and_conv_fn = + {tests: [test::test_desc], 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]; + let configport = port::<[u8]>(); + let tests = []; + for file: str in fs::list_dir(cx.config.src_base) { + let file = file; + log #fmt["inspecting file %s", file]; + if is_test(cx.config, file) { + tests += [make_test(cx, file, configport)]; + } + } + ret {tests: tests, to_task: bind closure_to_task(cx, configport, _)}; +} + +fn is_test(config: config, testfile: str) -> bool { + // Pretty-printer does not work with .rc files yet + let valid_extensions = + alt config.mode { mode_pretty. { [".rs"] } _ { [".rc", ".rs"] } }; + let invalid_prefixes = [".", "#", "~"]; + let name = fs::basename(testfile); + + let valid = false; + + for ext in valid_extensions { + if str::ends_with(name, ext) { valid = true; } + } + + for pre in invalid_prefixes { + if str::starts_with(name, pre) { valid = false; } + } + + ret valid; +} + +fn make_test(cx: cx, testfile: str, configport: port<[u8]>) -> + test::test_desc { + {name: make_test_name(cx.config, testfile), + fn: make_test_closure(testfile, chan(configport)), + ignore: header::is_test_ignored(cx.config, testfile)} +} + +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 { + bind send_config(testfile, configchan) +} + +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); +} + +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]) { + + test::configure_test_task(); + + 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}; + + let procsrv = procsrv::from_chan(procsrv_chan); + + let cx = {config: config, procsrv: procsrv}; + + runtest::run(cx, testfile); +} + +// Local Variables: +// fill-column: 78; +// indent-tabs-mode: nil +// c-basic-offset: 4 +// buffer-file-coding-system: utf-8-unix +// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'"; +// End: diff --git a/src/compiletest/header.rs b/src/compiletest/header.rs new file mode 100644 index 00000000000..890818d4574 --- /dev/null +++ b/src/compiletest/header.rs @@ -0,0 +1,117 @@ +import std::option; +import std::str; +import std::io; +import std::fs; + +import common::config; + +export test_props; +export load_props; +export is_test_ignored; + +type test_props = { + // Lines that should be expected, in order, on standard out + error_patterns: [str], + // Extra flags to pass to the compiler + compile_flags: option::t<str>, + // If present, the name of a file that this test should match when + // pretty-printed + pp_exact: option::t<str> +}; + +// Load any test directives embedded in the file +fn load_props(testfile: str) -> test_props { + let error_patterns = []; + let compile_flags = option::none; + let pp_exact = option::none; + for each ln: str in iter_header(testfile) { + alt parse_error_pattern(ln) { + option::some(ep) { error_patterns += [ep]; } + option::none. { } + } + + if option::is_none(compile_flags) { + compile_flags = parse_compile_flags(ln); + } + + if option::is_none(pp_exact) { + pp_exact = parse_pp_exact(ln, testfile); + } + } + ret { + error_patterns: error_patterns, + compile_flags: compile_flags, + pp_exact: pp_exact + }; +} + +fn is_test_ignored(config: config, testfile: str) -> bool { + let found = false; + for each ln: str in iter_header(testfile) { + // FIXME: Can't return or break from iterator + found = found || parse_name_directive(ln, "xfail-test"); + found = found || parse_name_directive(ln, xfail_target()); + if (config.mode == common::mode_pretty) { + found = found || parse_name_directive(ln, "xfail-pretty"); + } + } + ret found; + + fn xfail_target() -> str { + "xfail-" + std::os::target_os() + } +} + +iter iter_header(testfile: str) -> str { + let rdr = io::file_reader(testfile); + while !rdr.eof() { + let ln = rdr.read_line(); + + // Assume that any directives will be found before the first + // module or function. This doesn't seem to be an optimization + // with a warm page cache. Maybe with a cold one. + if str::starts_with(ln, "fn") + || str::starts_with(ln, "mod") { + break; + } else { put ln; } + } +} + +fn parse_error_pattern(line: str) -> option::t<str> { + parse_name_value_directive(line, "error-pattern") +} + +fn parse_compile_flags(line: str) -> option::t<str> { + parse_name_value_directive(line, "compile-flags") +} + +fn parse_pp_exact(line: str, testfile: str) -> option::t<str> { + alt parse_name_value_directive(line, "pp-exact") { + option::some(s) { option::some(s) } + option::none. { + if parse_name_directive(line, "pp-exact") { + option::some(fs::basename(testfile)) + } else { + option::none + } + } + } +} + +fn parse_name_directive(line: str, directive: str) -> bool { + str::find(line, directive) >= 0 +} + +fn parse_name_value_directive(line: str, + directive: str) -> option::t<str> { + let keycolon = directive + ":"; + if str::find(line, keycolon) >= 0 { + let colon = str::find(line, keycolon) as uint; + let value = + str::slice(line, colon + str::byte_len(keycolon), + str::byte_len(line)); + log #fmt("%s: %s", directive, + value); + option::some(value) + } else { option::none } +} diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs new file mode 100644 index 00000000000..6a40aaccaf4 --- /dev/null +++ b/src/compiletest/procsrv.rs @@ -0,0 +1,183 @@ +// So when running tests in parallel there's a potential race on environment +// variables if we let each task spawn its own children - between the time the +// environment is set and the process is spawned another task could spawn its +// child process. Because of that we have to use a complicated scheme with a +// dedicated server for spawning processes. + +import std::option; +import std::task; +import std::generic_os::setenv; +import std::generic_os::getenv; +import std::vec; +import std::os; +import std::run; +import std::io; +import std::str; +import std::comm::chan; +import std::comm::port; +import std::comm::send; +import std::comm::recv; + +export handle; +export mk; +export from_chan; +export run; +export close; +export reqchan; + +type reqchan = chan<request>; + +type handle = + {task: option::t<(task::task, port<task::task_notification>)>, + chan: reqchan}; + +tag request { exec([u8], [u8], [[u8]], chan<response>); stop; } + +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))); + ret {task: option::some(task), chan: recv(setupport)}; +} + +fn from_chan(ch: reqchan) -> handle { {task: option::none, chan: ch} } + +fn close(handle: handle) { + send(handle.chan, stop); + task::join(option::get(handle.task)); +} + +fn run(handle: handle, lib_path: str, prog: str, args: [str], + input: option::t<str>) -> {status: int, out: str, err: str} { + let p = port(); + let ch = chan(p); + send(handle.chan, + exec(str::bytes(lib_path), str::bytes(prog), clone_vecstr(args), + ch)); + let resp = recv(p); + + writeclose(resp.infd, input); + let output = readclose(resp.outfd); + let errput = readclose(resp.errfd); + let status = run::waitpid(resp.pid); + ret {status: status, out: output, err: errput}; +} + +fn writeclose(fd: int, s: option::t<str>) { + if option::is_some(s) { + let writer = io::new_writer(io::fd_buf_writer(fd, option::none)); + writer.write_str(option::get(s)); + } + + os::libc::close(fd); +} + +fn readclose(fd: int) -> str { + // Copied from run::program_output + let file = os::fd_FILE(fd); + let reader = io::new_reader(io::FILE_buf_reader(file, option::none)); + let buf = ""; + while !reader.eof() { + let bytes = reader.read_bytes(4096u); + buf += str::unsafe_from_bytes(bytes); + } + os::libc::fclose(file); + ret buf; +} + +fn worker(p: port<request>) { + + // FIXME (787): 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. + let execparms; + + while true { + // 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 (785): 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 recv(p) { + exec(lib_path, prog, args, respchan) { + {lib_path: str::unsafe_from_bytes(lib_path), + prog: str::unsafe_from_bytes(prog), + args: clone_vecu8str(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, execparms.args, + pipe_in.in, pipe_out.out, pipe_err.out); + let pid = with_lib_path(execparms.lib_path, spawnproc); + + os::libc::close(pipe_in.in); + os::libc::close(pipe_out.out); + os::libc::close(pipe_err.out); + if pid == -1 { + os::libc::close(pipe_in.out); + os::libc::close(pipe_out.in); + os::libc::close(pipe_err.in); + fail; + } + + send(execparms.respchan, + {pid: pid, + infd: pipe_in.out, + outfd: pipe_out.in, + errfd: pipe_err.in}); + } +} + +fn with_lib_path<@T>(path: str, f: fn() -> T) -> T { + let maybe_oldpath = getenv(util::lib_path_env_var()); + append_lib_path(path); + let res = f(); + if option::is_some(maybe_oldpath) { + export_lib_path(option::get(maybe_oldpath)); + } else { + // FIXME: This should really be unset but we don't have that yet + export_lib_path(""); + } + ret res; +} + +fn append_lib_path(path: str) { export_lib_path(util::make_new_path(path)); } + +fn export_lib_path(path: str) { setenv(util::lib_path_env_var(), path); } + +fn clone_vecstr(v: [str]) -> [[u8]] { + let r = []; + for t: str in vec::slice(v, 0u, vec::len(v)) { r += [str::bytes(t)]; } + ret r; +} + +fn clone_vecu8str(v: [[u8]]) -> [str] { + let r = []; + for t in vec::slice(v, 0u, vec::len(v)) { + r += [str::unsafe_from_bytes(t)]; + } + ret r; +} diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs new file mode 100644 index 00000000000..de188f3927e --- /dev/null +++ b/src/compiletest/runtest.rs @@ -0,0 +1,392 @@ +import std::io; +import std::str; +import std::option; +import std::fs; +import std::os; +import std::vec; +import std::test; + +import common::mode_run_pass; +import common::mode_run_fail; +import common::mode_compile_fail; +import common::mode_pretty; +import common::cx; +import common::config; +import header::load_props; +import header::test_props; +import util::logv; + +export run; + +fn run(cx: cx, -_testfile: [u8]) { + let testfile = str::unsafe_from_bytes(_testfile); + if cx.config.verbose { + // We're going to be dumping a lot of info. Start on a new line. + io::stdout().write_str("\n\n"); + } + 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_pretty. { run_pretty_test(cx, props, testfile); } + } +} + +fn run_cfail_test(cx: cx, props: test_props, testfile: str) { + let procres = compile_test(cx, props, testfile); + + if procres.status == 0 { + fatal_procres("compile-fail test compiled successfully!", procres); + } + + check_correct_failure_status(procres); + check_error_patterns(props, testfile, procres); +} + +fn run_rfail_test(cx: cx, props: test_props, testfile: str) { + let procres = compile_test(cx, props, testfile); + + if procres.status != 0 { fatal_procres("compilation failed!", procres); } + + procres = exec_compiled_test(cx, props, testfile); + + // The value our Makefile configures valgrind to return on failure + const valgrind_err: int = 100; + if procres.status == valgrind_err { + fatal_procres("run-fail test isn't valgrind-clean!", procres); + } + + check_correct_failure_status(procres); + check_error_patterns(props, testfile, procres); +} + +fn check_correct_failure_status(procres: procres) { + // The value the rust runtime returns on failure + const rust_err: int = 101; + if procres.status != rust_err { + fatal_procres( + #fmt("failure produced the wrong error code: %d", + procres.status), + procres); + } +} + +fn run_rpass_test(cx: cx, props: test_props, testfile: str) { + let procres = compile_test(cx, props, testfile); + + if procres.status != 0 { fatal_procres("compilation failed!", procres); } + + procres = exec_compiled_test(cx, props, testfile); + + + if procres.status != 0 { fatal_procres("test run failed!", procres); } +} + +fn run_pretty_test(cx: cx, props: test_props, testfile: str) { + if option::is_some(props.pp_exact) { + logv(cx.config, "testing for exact pretty-printing"); + } else { logv(cx.config, "testing for converging pretty-printing"); } + + let rounds = + alt props.pp_exact { option::some(_) { 1 } option::none. { 2 } }; + + let srcs = [io::read_whole_file_str(testfile)]; + + let round = 0; + while round < rounds { + logv(cx.config, #fmt["pretty-printing round %d", round]); + let procres = print_source(cx, testfile, srcs[round]); + + if procres.status != 0 { + fatal_procres(#fmt["pretty-printing failed in round %d", round], + procres); + } + + srcs += [procres.stdout]; + round += 1; + } + + let expected = + alt props.pp_exact { + option::some(file) { + let filepath = fs::connect(fs::dirname(testfile), file); + io::read_whole_file_str(filepath) + } + option::none. { srcs[vec::len(srcs) - 2u] } + }; + let actual = srcs[vec::len(srcs) - 1u]; + + if option::is_some(props.pp_exact) { + // Now we have to care about line endings + let cr = "\r"; + check (str::is_not_empty(cr)); + actual = str::replace(actual, cr, ""); + expected = str::replace(expected, cr, ""); + } + + compare_source(expected, actual); + + // Finally, let's make sure it actually appears to remain valid code + let procres = typecheck_source(cx, testfile, actual); + + if procres.status != 0 { + fatal_procres("pretty-printed source does not typecheck", procres); + } + + ret; + + fn print_source(cx: cx, testfile: str, src: str) -> procres { + compose_and_run(cx, testfile, make_pp_args, + cx.config.compile_lib_path, option::some(src)) + } + + fn make_pp_args(config: config, _testfile: str) -> procargs { + let prog = config.rustc_path; + let args = ["-", "--pretty", "normal"]; + ret {prog: prog, args: args}; + } + + fn compare_source(expected: str, actual: str) { + if expected != actual { + error("pretty-printed source does match expected source"); + let msg = + #fmt["\n\ +expected:\n\ +------------------------------------------\n\ +%s\n\ +------------------------------------------\n\ +actual:\n\ +------------------------------------------\n\ +%s\n\ +------------------------------------------\n\ +\n", + expected, actual]; + io::stdout().write_str(msg); + fail; + } + } + + fn typecheck_source(cx: cx, testfile: str, src: str) -> procres { + compose_and_run(cx, testfile, make_typecheck_args, + cx.config.compile_lib_path, option::some(src)) + } + + fn make_typecheck_args(config: config, _testfile: str) -> procargs { + let prog = config.rustc_path; + let args = ["-", "--no-trans", "--lib"]; + ret {prog: prog, args: args}; + } +} + +fn check_error_patterns(props: test_props, testfile: str, procres: procres) { + if vec::is_empty(props.error_patterns) { + fatal("no error pattern specified in " + testfile); + } + + if procres.status == 0 { + fatal("process did not return an error status"); + } + + let next_err_idx = 0u; + let next_err_pat = props.error_patterns[next_err_idx]; + for line: str in str::split(procres.stdout, '\n' as u8) { + if str::find(line, next_err_pat) > 0 { + log #fmt["found error pattern %s", next_err_pat]; + next_err_idx += 1u; + if next_err_idx == vec::len(props.error_patterns) { + log "found all error patterns"; + ret; + } + next_err_pat = props.error_patterns[next_err_idx]; + } + } + + let missing_patterns = + vec::slice(props.error_patterns, next_err_idx, + vec::len(props.error_patterns)); + if vec::len(missing_patterns) == 1u { + fatal_procres(#fmt["error pattern '%s' not found!", + missing_patterns[0]], procres); + } else { + for pattern: str in missing_patterns { + error(#fmt["error pattern '%s' not found!", pattern]); + } + fatal_procres("multiple error patterns not found", procres); + } +} + +type procargs = {prog: str, args: [str]}; + +type procres = {status: int, stdout: str, stderr: str, cmdline: str}; + +fn compile_test(cx: cx, props: test_props, testfile: str) -> procres { + compose_and_run(cx, testfile, bind make_compile_args(_, props, _), + cx.config.compile_lib_path, option::none) +} + +fn exec_compiled_test(cx: cx, props: test_props, testfile: str) -> procres { + compose_and_run(cx, testfile, bind make_run_args(_, props, _), + cx.config.run_lib_path, option::none) +} + +fn compose_and_run(cx: cx, testfile: str, + make_args: fn(config, str) -> procargs, lib_path: str, + input: option::t<str>) -> procres { + let procargs = make_args(cx.config, testfile); + ret program_output(cx, testfile, lib_path, procargs.prog, procargs.args, + input); +} + +fn make_compile_args(config: config, props: test_props, testfile: str) -> + procargs { + let prog = config.rustc_path; + let args = [testfile, "-o", make_exe_name(config, testfile)]; + let rustcflags = + alt config.rustcflags { + option::some(s) { option::some(s) } + option::none. { option::none } + }; + args += split_maybe_args(rustcflags); + args += split_maybe_args(props.compile_flags); + ret {prog: prog, args: args}; +} + +fn make_exe_name(config: config, testfile: str) -> str { + output_base_name(config, testfile) + os::exec_suffix() +} + +fn make_run_args(config: config, _props: test_props, testfile: str) -> + procargs { + let toolargs = { + // If we've got another tool to run under (valgrind), + // then split apart its command + let runtool = + alt config.runtool { + option::some(s) { option::some(s) } + option::none. { option::none } + }; + split_maybe_args(runtool) + }; + + let args = toolargs + [make_exe_name(config, testfile)]; + ret {prog: args[0], args: vec::slice(args, 1u, vec::len(args))}; +} + +fn split_maybe_args(argstr: option::t<str>) -> [str] { + fn rm_whitespace(v: [str]) -> [str] { + fn flt(s: str) -> option::t<str> { + if !is_whitespace(s) { option::some(s) } else { option::none } + } + + // FIXME: This should be in std + fn is_whitespace(s: str) -> bool { + for c: u8 in s { if c != ' ' as u8 { ret false; } } + ret true; + } + vec::filter_map(flt, v) + } + + alt argstr { + option::some(s) { rm_whitespace(str::split(s, ' ' as u8)) } + option::none. { [] } + } +} + +fn program_output(cx: cx, testfile: str, lib_path: str, prog: str, + args: [str], input: option::t<str>) -> procres { + let cmdline = + { + let cmdline = make_cmdline(lib_path, prog, args); + logv(cx.config, #fmt["executing %s", cmdline]); + cmdline + }; + let res = procsrv::run(cx.procsrv, lib_path, prog, args, input); + dump_output(cx.config, testfile, res.out, res.err); + ret {status: res.status, + stdout: res.out, + stderr: res.err, + cmdline: cmdline}; +} + +fn make_cmdline(libpath: str, prog: str, args: [str]) -> str { + #fmt["%s %s %s", lib_path_cmd_prefix(libpath), prog, + str::connect(args, " ")] +} + +// Build the LD_LIBRARY_PATH variable as it would be seen on the command line +// for diagnostic purposes +fn lib_path_cmd_prefix(path: str) -> str { + #fmt["%s=\"%s\"", util::lib_path_env_var(), util::make_new_path(path)] +} + +fn dump_output(config: config, testfile: str, out: str, err: str) { + dump_output_file(config, testfile, out, "out"); + dump_output_file(config, testfile, err, "err"); + maybe_dump_to_stdout(config, out, err); +} + +#[cfg(target_os = "win32")] +#[cfg(target_os = "linux")] +fn dump_output_file(config: config, testfile: str, out: str, extension: str) { + let outfile = make_out_name(config, testfile, extension); + let writer = io::file_writer(outfile, [io::create, io::truncate]); + writer.write_str(out); +} + +// FIXME (726): Can't use file_writer on mac +#[cfg(target_os = "macos")] +fn dump_output_file(config: config, testfile: str, out: str, extension: str) { +} + +fn make_out_name(config: config, testfile: str, extension: str) -> str { + output_base_name(config, testfile) + "." + extension +} + +fn output_base_name(config: config, testfile: str) -> str { + let base = config.build_base; + let filename = + { + let parts = str::split(fs::basename(testfile), '.' as u8); + parts = vec::slice(parts, 0u, vec::len(parts) - 1u); + str::connect(parts, ".") + }; + #fmt["%s%s.%s", base, filename, config.stage_id] +} + +fn maybe_dump_to_stdout(config: config, out: str, err: str) { + if config.verbose { + let sep1 = #fmt["------%s------------------------------", "stdout"]; + let sep2 = #fmt["------%s------------------------------", "stderr"]; + let sep3 = "------------------------------------------"; + io::stdout().write_line(sep1); + io::stdout().write_line(out); + io::stdout().write_line(sep2); + io::stdout().write_line(err); + io::stdout().write_line(sep3); + } +} + +fn error(err: str) { io::stdout().write_line(#fmt["\nerror: %s", err]); } + +fn fatal(err: str) -> ! { error(err); fail; } + +fn fatal_procres(err: str, procres: procres) -> ! { + let msg = + #fmt["\n\ +error: %s\n\ +command: %s\n\ +stdout:\n\ +------------------------------------------\n\ +%s\n\ +------------------------------------------\n\ +stderr:\n\ +------------------------------------------\n\ +%s\n\ +------------------------------------------\n\ +\n", + err, procres.cmdline, procres.stdout, procres.stderr]; + io::stdout().write_str(msg); + fail; +} diff --git a/src/compiletest/util.rs b/src/compiletest/util.rs new file mode 100644 index 00000000000..aa6c7d5abdb --- /dev/null +++ b/src/compiletest/util.rs @@ -0,0 +1,39 @@ +import std::option; +import std::generic_os::getenv; +import std::io; +import std::str; + +import common::config; + +fn make_new_path(path: str) -> str { + + // Windows just uses PATH as the library search path, so we have to + // maintain the current value while adding our own + alt getenv(lib_path_env_var()) { + option::some(curr) { + #fmt["%s%s%s", path, path_div(), curr] + } + option::none. { path } + } +} + +#[cfg(target_os = "linux")] +fn lib_path_env_var() -> str { "LD_LIBRARY_PATH" } + +#[cfg(target_os = "macos")] +fn lib_path_env_var() -> str { "DYLD_LIBRARY_PATH" } + +#[cfg(target_os = "win32")] +fn lib_path_env_var() -> str { "PATH" } + +#[cfg(target_os = "linux")] +#[cfg(target_os = "macos")] +fn path_div() -> str { ":" } + +#[cfg(target_os = "win32")] +fn path_div() -> str { ";" } + +fn logv(config: config, s: str) { + log s; + if config.verbose { io::stdout().write_line(s); } +} |
