Move compiletest to src/ and cleanup build rules

This commit is contained in:
Brian Anderson 2011-10-02 17:28:59 -07:00
parent edb56507ee
commit 3a6f3cf275
11 changed files with 60 additions and 48 deletions

32
src/compiletest/common.rs Normal file
View file

@ -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};

View file

@ -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:

View file

@ -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:

117
src/compiletest/header.rs Normal file
View file

@ -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 }
}

183
src/compiletest/procsrv.rs Normal file
View file

@ -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;
}

392
src/compiletest/runtest.rs Normal file
View file

@ -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;
}

39
src/compiletest/util.rs Normal file
View file

@ -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); }
}