Move compiletest modules into separate files

This commit is contained in:
Brian Anderson 2011-07-30 21:11:14 -07:00
parent 840a09c86e
commit a8af13e784
7 changed files with 616 additions and 586 deletions

View file

@ -0,0 +1,33 @@
import std::option;
tag mode { mode_compile_fail; mode_run_fail; mode_run_pass; }
type config = {
// The library paths required for running the compiler
compile_lib_path: str,
// The library paths required for running compiled programs
run_lib_path: str,
// The rustc executable
rustc_path: str,
// The directory containing the tests to run
src_base: str,
// The directory where programs should be built
build_base: str,
// The name of the stage being built (stage1, etc)
stage_id: str,
// The test mode, compile-fail, run-fail, run-pass
mode: mode,
// Run ignored tests
run_ignored: bool,
// Only run tests that match this filter
filter: option::t[str],
// A command line to prefix program execution with,
// for running under valgrind
runtool: option::t[str],
// Flags to pass to the compiler
rustcflags: option::t[str],
// Explain what's going on
verbose: bool
};
type cx = {config: config, procsrv: procsrv::handle};

View file

@ -1,6 +1,11 @@
use std;
mod compiletest;
mod procsrv;
mod util;
mod header;
mod runtest;
mod common;
// Local Variables:
// fill-column: 78;

View file

@ -5,43 +5,15 @@ import std::fs;
import std::str;
import std::vec;
import std::ivec;
import std::io;
import std::generic_os::setenv;
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; }
type config = {
// The library paths required for running the compiler
compile_lib_path: str,
// The library paths required for running compiled programs
run_lib_path: str,
// The rustc executable
rustc_path: str,
// The directory containing the tests to run
src_base: str,
// The directory where programs should be built
build_base: str,
// The name of the stage being built (stage1, etc)
stage_id: str,
// The test mode, compile-fail, run-fail, run-pass
mode: mode,
// Run ignored tests
run_ignored: bool,
// Only run tests that match this filter
filter: option::t[str],
// A command line to prefix program execution with,
// for running under valgrind
runtool: option::t[str],
// Flags to pass to the compiler
rustcflags: option::t[str],
// Explain what's going on
verbose: bool
};
import common::cx;
import common::config;
import common::mode_run_pass;
import common::mode_run_fail;
import common::mode_compile_fail;
import common::mode;
import util::logv;
fn main(args: vec[str]) {
@ -134,8 +106,6 @@ fn mode_str(mode: mode) -> str {
}
}
type cx = {config: config, procsrv: procsrv::handle};
fn run_tests(config: &config) {
let opts = test_opts(config);
let cx = {config: config, procsrv: procsrv::mk()};
@ -173,7 +143,7 @@ fn make_test(cx: &cx, testfile: &str, configport: &port[str]) ->
test::test_desc {
{name: testfile,
fn: make_test_closure(testfile, chan(configport)),
ignore: header::is_test_ignored(cx.config, testfile)}
ignore: header::is_test_ignored(cx.config.stage_id, testfile)}
}
/*
@ -253,554 +223,6 @@ fn run_test_task(compile_lib_path: str, run_lib_path: str, rustc_path: str,
runtest::run(cx, testfile);
}
fn make_cmdline(libpath: &str, prog: &str, args: &vec[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\"", lib_path_env_var(), make_new_path(path))
}
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", path, 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" }
fn make_exe_name(config: &config, testfile: &str) -> str {
output_base_name(config, testfile) + os::exec_suffix()
}
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 logv(config: &config, s: &str) {
log s;
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;
export load_props;
export is_test_ignored;
type test_props = {error_patterns: str[], compile_flags: 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;
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);
}
}
ret {error_patterns: error_patterns, compile_flags: compile_flags};
}
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-" + config.stage_id);
}
ret found;
}
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_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 }
}
}
mod runtest {
import header::load_props;
import header::test_props;
export run;
fn run(cx: &cx, testfile: &str) {
test::configure_test_task();
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); }
}
}
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_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, testfile);
if procres.status == 0 {
fatal_procres("run-fail test didn't produce an error!",
procres);
}
check_error_patterns(props, testfile, 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, testfile);
if procres.status != 0 { fatal_procres("test run failed!", procres); }
}
fn check_error_patterns(props: &test_props, testfile: &str,
procres: &procres) {
if ivec::is_empty(props.error_patterns) {
fatal("no error pattern specified in " + testfile);
}
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 == ivec::len(props.error_patterns) {
log "found all error patterns";
ret;
}
next_err_pat = props.error_patterns.(next_err_idx);
}
}
let missing_patterns =
ivec::slice(props.error_patterns, next_err_idx,
ivec::len(props.error_patterns));
if ivec::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: vec[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)
}
fn exec_compiled_test(cx: &cx, testfile: &str) -> procres {
compose_and_run(cx, testfile, make_run_args, cx.config.run_lib_path)
}
fn compose_and_run(cx: &cx, testfile: &str,
make_args: fn(&config, &str) -> procargs ,
lib_path: &str) -> procres {
let procargs = make_args(cx.config, testfile);
ret program_output(cx, testfile, lib_path,
procargs.prog, procargs.args);
}
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)];
args += split_maybe_args(config.rustcflags);
args += split_maybe_args(props.compile_flags);
ret {prog: prog, args: args};
}
fn make_run_args(config: &config, testfile: &str) -> procargs {
// If we've got another tool to run under (valgrind),
// then split apart its command
let args =
split_maybe_args(config.runtool)
+ [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]) -> vec[str] {
fn rm_whitespace(v: vec[str]) -> vec[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: &vec[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);
dump_output(cx.config, testfile, res.out, res.err);
ret {status: res.status, stdout: res.out,
stderr: res.err, cmdline: cmdline};
}
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 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;
}
}
// 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.
mod procsrv {
export handle;
export mk;
export from_chan;
export clone;
export run;
export close;
export reqchan;
type reqchan = chan[request];
type handle = {task: option::t[task], chan: reqchan};
tag request { exec(str, str, str[], chan[response]); stop; }
type response = {pid: int, outfd: int, errfd: int};
fn mk() -> handle {
let setupport = port();
let task = spawn fn(setupchan: chan[chan[request]]) {
let reqport = port();
let reqchan = chan(reqport);
task::send(setupchan, task::clone_chan(reqchan));
worker(reqport);
} (chan(setupport));
ret {task: option::some(task),
chan: task::recv(setupport)
};
}
fn from_chan(ch: &reqchan) -> handle { {task: option::none, chan: ch} }
fn clone(handle: &handle) -> handle {
// Sharing tasks across tasks appears to be (yet another) recipe for
// disaster, so our handle clones will not get the task pointer.
{task: option::none, chan: task::clone_chan(handle.chan)}
}
fn close(handle: &handle) {
task::send(handle.chan, stop);
task::join(option::get(handle.task));
}
fn run(handle: &handle, lib_path: &str, prog: &str, args: &vec[str]) ->
{status: int, out: str, err: str} {
let p = port[response]();
let ch = chan(p);
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);
let status = os::waitpid(resp.pid);
ret {status: status, out: output, err: errput};
}
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, 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 {
// 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 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);
os::libc::close(pipe_in.in);
os::libc::close(pipe_in.out);
os::libc::close(pipe_out.out);
os::libc::close(pipe_err.out);
if pid == -1 {
os::libc::close(pipe_out.in);
os::libc::close(pipe_err.in);
fail;
}
task::send(execparms.respchan,
{pid: pid,
outfd: pipe_out.in,
errfd: pipe_err.in});
}
}
fn with_lib_path[T](path: &str, f: fn() -> T ) -> T {
let maybe_oldpath = getenv(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(make_new_path(path)); }
fn export_lib_path(path: &str) { setenv(lib_path_env_var(), path); }
}
// Local Variables:
// fill-column: 78;
// indent-tabs-mode: nil

View file

@ -0,0 +1,76 @@
import std::option;
import std::str;
import std::io;
export test_props;
export load_props;
export is_test_ignored;
type test_props = {error_patterns: str[], compile_flags: 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;
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);
}
}
ret {error_patterns: error_patterns, compile_flags: compile_flags};
}
fn is_test_ignored(stage_id: &str, 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-" + stage_id);
}
ret found;
}
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_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 }
}

View file

@ -0,0 +1,186 @@
// 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::ivec;
import std::os;
import std::run;
import std::unsafe;
import std::io;
import std::str;
export handle;
export mk;
export from_chan;
export run;
export close;
export reqchan;
type reqchan = chan[request];
type handle = {task: option::t[task], chan: reqchan};
tag request { exec(str, str, str[], chan[response]); stop; }
type response = {pid: int, outfd: int, errfd: int};
fn mk() -> handle {
let setupport = port();
let task = spawn fn(setupchan: chan[chan[request]]) {
let reqport = port();
let reqchan = chan(reqport);
task::send(setupchan, task::clone_chan(reqchan));
worker(reqport);
} (chan(setupport));
ret {task: option::some(task),
chan: task::recv(setupport)
};
}
fn from_chan(ch: &reqchan) -> handle { {task: option::none, chan: ch} }
fn clone(handle: &handle) -> handle {
// Sharing tasks across tasks appears to be (yet another) recipe for
// disaster, so our handle clones will not get the task pointer.
{task: option::none, chan: task::clone_chan(handle.chan)}
}
fn close(handle: &handle) {
task::send(handle.chan, stop);
task::join(option::get(handle.task));
}
fn run(handle: &handle, lib_path: &str, prog: &str, args: &vec[str]) ->
{status: int, out: str, err: str} {
let p = port[response]();
let ch = chan(p);
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);
let status = os::waitpid(resp.pid);
ret {status: status, out: output, err: errput};
}
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, 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 {
// 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 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);
os::libc::close(pipe_in.in);
os::libc::close(pipe_in.out);
os::libc::close(pipe_out.out);
os::libc::close(pipe_err.out);
if pid == -1 {
os::libc::close(pipe_out.in);
os::libc::close(pipe_err.in);
fail;
}
task::send(execparms.respchan,
{pid: pid,
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_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;
}

View file

@ -0,0 +1,279 @@
import std::io;
import std::str;
import std::option;
import std::vec;
import std::fs;
import std::os;
import std::ivec;
import std::test;
import common::mode_run_pass;
import common::mode_run_fail;
import common::mode_compile_fail;
import common::cx;
import common::config;
import header::load_props;
import header::test_props;
import util::logv;
export run;
fn run(cx: &cx, testfile: &str) {
test::configure_test_task();
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); }
}
}
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_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, testfile);
if procres.status == 0 {
fatal_procres("run-fail test didn't produce an error!",
procres);
}
check_error_patterns(props, testfile, 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, testfile);
if procres.status != 0 { fatal_procres("test run failed!", procres); }
}
fn check_error_patterns(props: &test_props, testfile: &str,
procres: &procres) {
if ivec::is_empty(props.error_patterns) {
fatal("no error pattern specified in " + testfile);
}
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 == ivec::len(props.error_patterns) {
log "found all error patterns";
ret;
}
next_err_pat = props.error_patterns.(next_err_idx);
}
}
let missing_patterns =
ivec::slice(props.error_patterns, next_err_idx,
ivec::len(props.error_patterns));
if ivec::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: vec[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)
}
fn exec_compiled_test(cx: &cx, testfile: &str) -> procres {
compose_and_run(cx, testfile, make_run_args, cx.config.run_lib_path)
}
fn compose_and_run(cx: &cx, testfile: &str,
make_args: fn(&config, &str) -> procargs ,
lib_path: &str) -> procres {
let procargs = make_args(cx.config, testfile);
ret program_output(cx, testfile, lib_path,
procargs.prog, procargs.args);
}
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)];
args += split_maybe_args(config.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, testfile: &str) -> procargs {
// If we've got another tool to run under (valgrind),
// then split apart its command
let args =
split_maybe_args(config.runtool)
+ [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]) -> vec[str] {
fn rm_whitespace(v: vec[str]) -> vec[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: &vec[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);
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: &vec[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;
}

View file

@ -0,0 +1,29 @@
import std::option;
import std::generic_os::getenv;
import std::io;
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", path, 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" }
fn logv(config: &config, s: &str) {
log s;
if config.verbose { io::stdout().write_line(s); }
}