// Copyright 2012-2013 The Rust Project Developers. See the // COPYRIGHT file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use common::mode_run_pass; use common::mode_run_fail; use common::mode_compile_fail; use common::mode_pretty; use common::config; use errors; use header::load_props; use header::TestProps; use procsrv; use util; use util::logv; pub fn run(config: config, testfile: ~str) { if config.verbose { // We're going to be dumping a lot of info. Start on a new line. io::stdout().write_str(~"\n\n"); } let testfile = Path(testfile); debug!("running %s", testfile.to_str()); let props = load_props(&testfile); debug!("loaded props"); match config.mode { mode_compile_fail => run_cfail_test(&config, &props, &testfile), mode_run_fail => run_rfail_test(&config, &props, &testfile), mode_run_pass => run_rpass_test(&config, &props, &testfile), mode_pretty => run_pretty_test(&config, &props, &testfile), mode_debug_info => run_debuginfo_test(&config, &props, &testfile) } } fn run_cfail_test(config: &config, props: &TestProps, testfile: &Path) { let ProcRes = compile_test(config, props, testfile); if ProcRes.status == 0 { fatal_ProcRes(~"compile-fail test compiled successfully!", &ProcRes); } check_correct_failure_status(&ProcRes); let expected_errors = errors::load_errors(testfile); if !expected_errors.is_empty() { if !props.error_patterns.is_empty() { fatal(~"both error pattern and expected errors specified"); } check_expected_errors(expected_errors, testfile, &ProcRes); } else { check_error_patterns(props, testfile, &ProcRes); } } fn run_rfail_test(config: &config, props: &TestProps, testfile: &Path) { let ProcRes = if !config.jit { let ProcRes = compile_test(config, props, testfile); if ProcRes.status != 0 { fatal_ProcRes(~"compilation failed!", &ProcRes); } exec_compiled_test(config, props, testfile) } else { jit_test(config, props, testfile) }; // The value our Makefile configures valgrind to return on failure static valgrind_err: int = 100; if ProcRes.status == valgrind_err { fatal_ProcRes(~"run-fail test isn't valgrind-clean!", &ProcRes); } match config.target { ~"arm-linux-androideabi" => { if (config.adb_device_status) { check_correct_failure_status(&ProcRes); check_error_patterns(props, testfile, &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 static 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(config: &config, props: &TestProps, testfile: &Path) { if !config.jit { let mut ProcRes = compile_test(config, props, testfile); if ProcRes.status != 0 { fatal_ProcRes(~"compilation failed!", &ProcRes); } ProcRes = exec_compiled_test(config, props, testfile); if ProcRes.status != 0 { fatal_ProcRes(~"test run failed!", &ProcRes); } } else { let ProcRes = jit_test(config, props, testfile); if ProcRes.status != 0 { fatal_ProcRes(~"jit failed!", &ProcRes); } } } fn run_pretty_test(config: &config, props: &TestProps, testfile: &Path) { if props.pp_exact.is_some() { logv(config, ~"testing for exact pretty-printing"); } else { logv(config, ~"testing for converging pretty-printing"); } let rounds = match props.pp_exact { Some(_) => 1, None => 2 }; let mut srcs = ~[io::read_whole_file_str(testfile).get()]; let mut round = 0; while round < rounds { logv(config, fmt!("pretty-printing round %d", round)); let ProcRes = print_source(config, testfile, copy srcs[round]); if ProcRes.status != 0 { fatal_ProcRes(fmt!("pretty-printing failed in round %d", round), &ProcRes); } let ProcRes{ stdout, _ } = ProcRes; srcs.push(stdout); round += 1; } let mut expected = match props.pp_exact { Some(ref file) => { let filepath = testfile.dir_path().push_rel(file); io::read_whole_file_str(&filepath).get() } None => { copy srcs[srcs.len() - 2u] } }; let mut actual = copy srcs[srcs.len() - 1u]; if props.pp_exact.is_some() { // Now we have to care about line endings let cr = ~"\r"; 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(config, props, testfile, actual); if ProcRes.status != 0 { fatal_ProcRes(~"pretty-printed source does not typecheck", &ProcRes); } return; fn print_source(config: &config, testfile: &Path, src: ~str) -> ProcRes { compose_and_run(config, testfile, make_pp_args(config, testfile), ~[], config.compile_lib_path, Some(src)) } fn make_pp_args(config: &config, _testfile: &Path) -> ProcArgs { let args = ~[~"-", ~"--pretty", ~"normal"]; return ProcArgs {prog: config.rustc_path.to_str(), args: args}; } fn compare_source(expected: &str, actual: &str) { if expected != actual { error(~"pretty-printed source does not 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(config: &config, props: &TestProps, testfile: &Path, src: ~str) -> ProcRes { let args = make_typecheck_args(config, props, testfile); compose_and_run_compiler(config, props, testfile, args, Some(src)) } fn make_typecheck_args(config: &config, props: &TestProps, testfile: &Path) -> ProcArgs { let mut args = ~[~"-", ~"--no-trans", ~"--lib", ~"-L", config.build_base.to_str(), ~"-L", aux_output_dir_name(config, testfile).to_str()]; args += split_maybe_args(&config.rustcflags); args += split_maybe_args(&props.compile_flags); return ProcArgs {prog: config.rustc_path.to_str(), args: args}; } } fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) { // do not optimize debuginfo tests let mut config = match config.rustcflags { Some(ref flags) => config { rustcflags: Some(str::replace(*flags, ~"-O", ~"")), .. copy *config }, None => copy *config }; let config = &mut config; let cmds = str::connect(props.debugger_cmds, "\n"); let check_lines = copy props.check_lines; // compile test file (it shoud have 'compile-flags:-g' in the header) let mut ProcRes = compile_test(config, props, testfile); if ProcRes.status != 0 { fatal_ProcRes(~"compilation failed!", &ProcRes); } // write debugger script let script_str = str::append(cmds, "\nquit\n"); debug!("script_str = %s", script_str); dump_output_file(config, testfile, script_str, ~"debugger.script"); // run debugger script with gdb #[cfg(windows)] fn debugger() -> ~str { ~"gdb.exe" } #[cfg(unix)] fn debugger() -> ~str { ~"gdb" } let debugger_script = make_out_name(config, testfile, ~"debugger.script"); let debugger_opts = ~[~"-quiet", ~"-batch", ~"-nx", ~"-command=" + debugger_script.to_str(), make_exe_name(config, testfile).to_str()]; let ProcArgs = ProcArgs {prog: debugger(), args: debugger_opts}; ProcRes = compose_and_run(config, testfile, ProcArgs, ~[], ~"", None); if ProcRes.status != 0 { fatal(~"gdb failed to execute"); } let num_check_lines = check_lines.len(); if num_check_lines > 0 { // check if each line in props.check_lines appears in the // output (in order) let mut i = 0u; for str::each_line(ProcRes.stdout) |line| { if check_lines[i].trim() == line.trim() { i += 1u; } if i == num_check_lines { // all lines checked break; } } if i != num_check_lines { fatal_ProcRes(fmt!("line not found in debugger output: %s" check_lines[i]), &ProcRes); } } } fn check_error_patterns(props: &TestProps, testfile: &Path, ProcRes: &ProcRes) { if vec::is_empty(props.error_patterns) { fatal(~"no error pattern specified in " + testfile.to_str()); } if ProcRes.status == 0 { fatal(~"process did not return an error status"); } let mut next_err_idx = 0u; let mut next_err_pat = &props.error_patterns[next_err_idx]; let mut done = false; for str::each_line(ProcRes.stderr) |line| { if str::contains(line, *next_err_pat) { debug!("found error pattern %s", *next_err_pat); next_err_idx += 1u; if next_err_idx == props.error_patterns.len() { debug!("found all error patterns"); done = true; break; } next_err_pat = &props.error_patterns[next_err_idx]; } } if done { return; } let missing_patterns = vec::slice(props.error_patterns, next_err_idx, props.error_patterns.len()); if missing_patterns.len() == 1u { fatal_ProcRes(fmt!("error pattern '%s' not found!", missing_patterns[0]), ProcRes); } else { for missing_patterns.each |pattern| { error(fmt!("error pattern '%s' not found!", *pattern)); } fatal_ProcRes(~"multiple error patterns not found", ProcRes); } } fn check_expected_errors(expected_errors: ~[errors::ExpectedError], testfile: &Path, ProcRes: &ProcRes) { // true if we found the error in question let mut found_flags = vec::from_elem( expected_errors.len(), false); if ProcRes.status == 0 { fatal(~"process did not return an error status"); } let prefixes = vec::map(expected_errors, |ee| { fmt!("%s:%u:", testfile.to_str(), ee.line) }); // Scan and extract our error/warning messages, // which look like: // filename:line1:col1: line2:col2: *error:* msg // filename:line1:col1: line2:col2: *warning:* msg // where line1:col1: is the starting point, line2:col2: // is the ending point, and * represents ANSI color codes. for str::each_line(ProcRes.stderr) |line| { let mut was_expected = false; for vec::eachi(expected_errors) |i, ee| { if !found_flags[i] { debug!("prefix=%s ee.kind=%s ee.msg=%s line=%s", prefixes[i], ee.kind, ee.msg, line); if (str::starts_with(line, prefixes[i]) && str::contains(line, ee.kind) && str::contains(line, ee.msg)) { found_flags[i] = true; was_expected = true; break; } } } // ignore this msg which gets printed at the end if str::contains(line, ~"aborting due to") { was_expected = true; } if !was_expected && is_compiler_error_or_warning(str::to_owned(line)) { fatal_ProcRes(fmt!("unexpected compiler error or warning: '%s'", line), ProcRes); } } for uint::range(0u, found_flags.len()) |i| { if !found_flags[i] { let ee = &expected_errors[i]; fatal_ProcRes(fmt!("expected %s on line %u not found: %s", ee.kind, ee.line, ee.msg), ProcRes); } } } fn is_compiler_error_or_warning(line: &str) -> bool { let mut i = 0u; return scan_until_char(line, ':', &mut i) && scan_char(line, ':', &mut i) && scan_integer(line, &mut i) && scan_char(line, ':', &mut i) && scan_integer(line, &mut i) && scan_char(line, ':', &mut i) && scan_char(line, ' ', &mut i) && scan_integer(line, &mut i) && scan_char(line, ':', &mut i) && scan_integer(line, &mut i) && scan_char(line, ' ', &mut i) && (scan_string(line, "error", &mut i) || scan_string(line, "warning", &mut i)); } fn scan_until_char(haystack: &str, needle: char, idx: &mut uint) -> bool { if *idx >= haystack.len() { return false; } let opt = str::find_char_from(haystack, needle, *idx); if opt.is_none() { return false; } *idx = opt.get(); return true; } fn scan_char(haystack: &str, needle: char, idx: &mut uint) -> bool { if *idx >= haystack.len() { return false; } let range = str::char_range_at(haystack, *idx); if range.ch != needle { return false; } *idx = range.next; return true; } fn scan_integer(haystack: &str, idx: &mut uint) -> bool { let mut i = *idx; while i < haystack.len() { let range = str::char_range_at(haystack, i); if range.ch < '0' || '9' < range.ch { break; } i = range.next; } if i == *idx { return false; } *idx = i; return true; } fn scan_string(haystack: &str, needle: &str, idx: &mut uint) -> bool { let mut haystack_i = *idx; let mut needle_i = 0u; while needle_i < needle.len() { if haystack_i >= haystack.len() { return false; } let range = str::char_range_at(haystack, haystack_i); haystack_i = range.next; if !scan_char(needle, range.ch, &mut needle_i) { return false; } } *idx = haystack_i; return true; } struct ProcArgs {prog: ~str, args: ~[~str]} struct ProcRes {status: int, stdout: ~str, stderr: ~str, cmdline: ~str} fn compile_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { compile_test_(config, props, testfile, []) } fn jit_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { compile_test_(config, props, testfile, [~"--jit"]) } fn compile_test_(config: &config, props: &TestProps, testfile: &Path, extra_args: &[~str]) -> ProcRes { let link_args = ~[~"-L", aux_output_dir_name(config, testfile).to_str()]; let args = make_compile_args(config, props, link_args + extra_args, make_exe_name, testfile); compose_and_run_compiler(config, props, testfile, args, None) } fn exec_compiled_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { // If testing the new runtime then set the RUST_NEWRT env var let env = copy props.exec_env; let env = if config.newrt { env + &[(~"RUST_NEWRT", ~"1")] } else { env }; match config.target { ~"arm-linux-androideabi" => { if (config.adb_device_status) { _arm_exec_compiled_test(config, props, testfile) } else { _dummy_exec_compiled_test(config, props, testfile) } } _=> { compose_and_run(config, testfile, make_run_args(config, props, testfile), env, config.run_lib_path, None) } } } fn compose_and_run_compiler( config: &config, props: &TestProps, testfile: &Path, args: ProcArgs, input: Option<~str>) -> ProcRes { if !props.aux_builds.is_empty() { ensure_dir(&aux_output_dir_name(config, testfile)); } let extra_link_args = ~[~"-L", aux_output_dir_name(config, testfile).to_str()]; for vec::each(props.aux_builds) |rel_ab| { let abs_ab = config.aux_base.push_rel(&Path(*rel_ab)); let aux_args = make_compile_args(config, props, ~[~"--lib"] + extra_link_args, |a,b| make_lib_name(a, b, testfile), &abs_ab); let auxres = compose_and_run(config, &abs_ab, aux_args, ~[], config.compile_lib_path, None); if auxres.status != 0 { fatal_ProcRes( fmt!("auxiliary build of %s failed to compile: ", abs_ab.to_str()), &auxres); } match config.target { ~"arm-linux-androideabi" => { if (config.adb_device_status) { _arm_push_aux_shared_library(config, testfile); } } _=> { } } } compose_and_run(config, testfile, args, ~[], config.compile_lib_path, input) } fn ensure_dir(path: &Path) { if os::path_is_dir(path) { return; } if !os::make_dir(path, 0x1c0i32) { fail!("can't make dir %s", path.to_str()); } } fn compose_and_run(config: &config, testfile: &Path, ProcArgs{ args, prog }: ProcArgs, procenv: ~[(~str, ~str)], lib_path: &str, input: Option<~str>) -> ProcRes { return program_output(config, testfile, lib_path, prog, args, procenv, input); } fn make_compile_args(config: &config, props: &TestProps, extras: ~[~str], xform: &fn(&config, (&Path)) -> Path, testfile: &Path) -> ProcArgs { let mut args = ~[testfile.to_str(), ~"-o", xform(config, testfile).to_str(), ~"-L", config.build_base.to_str()] + extras; args += split_maybe_args(&config.rustcflags); args += split_maybe_args(&props.compile_flags); return ProcArgs {prog: config.rustc_path.to_str(), args: args}; } fn make_lib_name(config: &config, auxfile: &Path, testfile: &Path) -> Path { // what we return here is not particularly important, as it // happens; rustc ignores everything except for the directory. let auxname = output_testname(auxfile); aux_output_dir_name(config, testfile).push_rel(&auxname) } fn make_exe_name(config: &config, testfile: &Path) -> Path { Path(output_base_name(config, testfile).to_str() + str::to_owned(os::EXE_SUFFIX)) } fn make_run_args(config: &config, _props: &TestProps, testfile: &Path) -> ProcArgs { // If we've got another tool to run under (valgrind), // then split apart its command let toolargs = split_maybe_args(&config.runtool); let mut args = toolargs + ~[make_exe_name(config, testfile).to_str()]; let prog = args.shift(); return ProcArgs {prog: prog, args: args}; } fn split_maybe_args(argstr: &Option<~str>) -> ~[~str] { fn rm_whitespace(v: ~[~str]) -> ~[~str] { v.filtered(|s| !str::is_whitespace(*s)) } match *argstr { Some(ref s) => { let mut ss = ~[]; for str::each_split_char(*s, ' ') |s| { ss.push(s.to_owned()) } rm_whitespace(ss) } None => ~[] } } fn program_output(config: &config, testfile: &Path, lib_path: &str, prog: ~str, args: ~[~str], env: ~[(~str, ~str)], input: Option<~str>) -> ProcRes { let cmdline = { let cmdline = make_cmdline(lib_path, prog, args); logv(config, fmt!("executing %s", cmdline)); cmdline }; let procsrv::Result{ out, err, status } = procsrv::run(lib_path, prog, args, env, input); dump_output(config, testfile, out, err); return ProcRes {status: status, stdout: out, stderr: err, cmdline: cmdline}; } // Linux and mac don't require adjusting the library search path #[cfg(target_os = "linux")] #[cfg(target_os = "macos")] #[cfg(target_os = "freebsd")] fn make_cmdline(_libpath: &str, prog: &str, args: &[~str]) -> ~str { fmt!("%s %s", prog, str::connect(args, ~" ")) } #[cfg(target_os = "win32")] 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: &Path, 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); } fn dump_output_file(config: &config, testfile: &Path, out: &str, extension: &str) { let outfile = make_out_name(config, testfile, extension); let writer = io::file_writer(&outfile, ~[io::Create, io::Truncate]).get(); writer.write_str(out); } fn make_out_name(config: &config, testfile: &Path, extension: &str) -> Path { output_base_name(config, testfile).with_filetype(extension) } fn aux_output_dir_name(config: &config, testfile: &Path) -> Path { output_base_name(config, testfile).with_filetype("libaux") } fn output_testname(testfile: &Path) -> Path { Path(testfile.filestem().get()) } fn output_base_name(config: &config, testfile: &Path) -> Path { config.build_base .push_rel(&output_testname(testfile)) .with_filetype(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!(); } fn _arm_exec_compiled_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { let args = make_run_args(config, props, testfile); let cmdline = make_cmdline("", args.prog, args.args); // get bare program string let mut tvec = ~[]; for str::each_split_char(args.prog, '/') |ts| { tvec.push(ts.to_owned()) } let prog_short = tvec.pop(); // copy to target let copy_result = procsrv::run("", config.adb_path, [~"push", copy args.prog, copy config.adb_test_dir], ~[(~"",~"")], Some(~"")); if config.verbose { io::stdout().write_str(fmt!("push (%s) %s %s %s", config.target, args.prog, copy_result.out, copy_result.err)); } // execute program logv(config, fmt!("executing (%s) %s", config.target, cmdline)); // adb shell dose not forward stdout and stderr of internal result // to stdout and stderr separately but to stdout only let mut newargs_out = ~[]; let mut newargs_err = ~[]; newargs_out.push(~"shell"); newargs_err.push(~"shell"); let mut newcmd_out = ~""; let mut newcmd_err = ~""; newcmd_out.push_str(fmt!("LD_LIBRARY_PATH=%s %s/%s", config.adb_test_dir, config.adb_test_dir, prog_short)); newcmd_err.push_str(fmt!("LD_LIBRARY_PATH=%s %s/%s", config.adb_test_dir, config.adb_test_dir, prog_short)); for args.args.each |tv| { newcmd_out.push_str(" "); newcmd_err.push_str(" "); newcmd_out.push_str(tv.to_owned()); newcmd_err.push_str(tv.to_owned()); } newcmd_out.push_str(" 2>/dev/null"); newcmd_err.push_str(" 1>/dev/null"); newargs_out.push(newcmd_out); newargs_err.push(newcmd_err); let procsrv::Result{ out: out_out, err: _out_err, status: out_status } = procsrv::run(~"", config.adb_path, newargs_out, ~[(~"",~"")], Some(~"")); let procsrv::Result{ out: err_out, err: _err_err, status: _err_status } = procsrv::run(~"", config.adb_path, newargs_err, ~[(~"",~"")], Some(~"")); dump_output(config, testfile, out_out, err_out); match err_out { ~"" => ProcRes {status: out_status, stdout: out_out, stderr: err_out, cmdline: cmdline }, _ => ProcRes {status: 101, stdout: out_out, stderr: err_out, cmdline: cmdline } } } fn _dummy_exec_compiled_test(config: &config, props: &TestProps, testfile: &Path) -> ProcRes { let args = make_run_args(config, props, testfile); let cmdline = make_cmdline("", args.prog, args.args); match config.mode { mode_run_fail => ProcRes {status: 101, stdout: ~"", stderr: ~"", cmdline: cmdline}, _ => ProcRes {status: 0, stdout: ~"", stderr: ~"", cmdline: cmdline} } } fn _arm_push_aux_shared_library(config: &config, testfile: &Path) { let tstr = aux_output_dir_name(config, testfile).to_str(); for os::list_dir_path(&Path(tstr)).each |file| { if (file.filetype() == Some(~".so")) { let copy_result = procsrv::run(~"", config.adb_path, ~[~"push", file.to_str(), copy config.adb_test_dir], ~[(~"",~"")], Some(~"")); if config.verbose { io::stdout().write_str(fmt!("push (%s) %s %s %s", config.target, file.to_str(), copy_result.out, copy_result.err)); } } } }