// Copyright 2012-2014 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 self::TargetLocation::*; use common::Config; use common::{CompileFail, Pretty, RunFail, RunPass, RunPassValgrind, DebugInfoGdb}; use common::{Codegen, DebugInfoLldb}; use errors; use header::TestProps; use header; use procsrv; use util::logv; #[cfg(target_os = "windows")] use util; #[cfg(target_os = "windows")] use std::ascii::AsciiExt; use std::old_io::File; use std::old_io::fs::PathExtensions; use std::old_io::fs; use std::old_io::net::tcp; use std::old_io::process::ProcessExit; use std::old_io::process; use std::old_io::timer; use std::old_io; use std::os; use std::iter::repeat; use std::str; use std::string::String; use std::thread::Thread; use std::time::Duration; use test::MetricMap; pub fn run(config: Config, testfile: String) { match config.target.as_slice() { "arm-linux-androideabi" => { if !config.adb_device_status { panic!("android device not available"); } } _=> { } } let mut _mm = MetricMap::new(); run_metrics(config, testfile, &mut _mm); } pub fn run_metrics(config: Config, testfile: String, mm: &mut MetricMap) { if config.verbose { // We're going to be dumping a lot of info. Start on a new line. print!("\n\n"); } let testfile = Path::new(testfile); debug!("running {:?}", testfile.display()); let props = header::load_props(&testfile); debug!("loaded props"); match config.mode { CompileFail => run_cfail_test(&config, &props, &testfile), RunFail => run_rfail_test(&config, &props, &testfile), RunPass => run_rpass_test(&config, &props, &testfile), RunPassValgrind => run_valgrind_test(&config, &props, &testfile), Pretty => run_pretty_test(&config, &props, &testfile), DebugInfoGdb => run_debuginfo_gdb_test(&config, &props, &testfile), DebugInfoLldb => run_debuginfo_lldb_test(&config, &props, &testfile), Codegen => run_codegen_test(&config, &props, &testfile, mm), } } fn get_output(props: &TestProps, proc_res: &ProcRes) -> String { if props.check_stdout { format!("{}{}", proc_res.stdout, proc_res.stderr) } else { proc_res.stderr.clone() } } fn run_cfail_test(config: &Config, props: &TestProps, testfile: &Path) { let proc_res = compile_test(config, props, testfile); if proc_res.status.success() { fatal_proc_rec("compile-fail test compiled successfully!", &proc_res); } check_correct_failure_status(&proc_res); if proc_res.status.success() { fatal("process did not return an error status"); } let output_to_check = get_output(props, &proc_res); 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, &proc_res); } else { check_error_patterns(props, testfile, output_to_check.as_slice(), &proc_res); } check_no_compiler_crash(&proc_res); check_forbid_output(props, output_to_check.as_slice(), &proc_res); } fn run_rfail_test(config: &Config, props: &TestProps, testfile: &Path) { let proc_res = if !config.jit { let proc_res = compile_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } 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 proc_res.status.matches_exit_status(VALGRIND_ERR) { fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res); } let output_to_check = get_output(props, &proc_res); check_correct_failure_status(&proc_res); check_error_patterns(props, testfile, output_to_check.as_slice(), &proc_res); } fn check_correct_failure_status(proc_res: &ProcRes) { // The value the rust runtime returns on failure static RUST_ERR: int = 101; if !proc_res.status.matches_exit_status(RUST_ERR) { fatal_proc_rec( format!("failure produced the wrong error: {:?}", proc_res.status).as_slice(), proc_res); } } fn run_rpass_test(config: &Config, props: &TestProps, testfile: &Path) { if !config.jit { let mut proc_res = compile_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } proc_res = exec_compiled_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("test run failed!", &proc_res); } } else { let proc_res = jit_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("jit failed!", &proc_res); } } } fn run_valgrind_test(config: &Config, props: &TestProps, testfile: &Path) { if config.valgrind_path.is_none() { assert!(!config.force_valgrind); return run_rpass_test(config, props, testfile); } let mut proc_res = compile_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } let mut new_config = config.clone(); new_config.runtool = new_config.valgrind_path.clone(); proc_res = exec_compiled_test(&new_config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("test run failed!", &proc_res); } } fn run_pretty_test(config: &Config, props: &TestProps, testfile: &Path) { if props.pp_exact.is_some() { logv(config, "testing for exact pretty-printing".to_string()); } else { logv(config, "testing for converging pretty-printing".to_string()); } let rounds = match props.pp_exact { Some(_) => 1, None => 2 }; let src = File::open(testfile).read_to_end().unwrap(); let src = String::from_utf8(src.clone()).unwrap(); let mut srcs = vec!(src); let mut round = 0; while round < rounds { logv(config, format!("pretty-printing round {}", round)); let proc_res = print_source(config, props, testfile, srcs[round].to_string(), props.pretty_mode.as_slice()); if !proc_res.status.success() { fatal_proc_rec(format!("pretty-printing failed in round {}", round).as_slice(), &proc_res); } let ProcRes{ stdout, .. } = proc_res; srcs.push(stdout); round += 1; } let mut expected = match props.pp_exact { Some(ref file) => { let filepath = testfile.dir_path().join(file); let s = File::open(&filepath).read_to_end().unwrap(); String::from_utf8(s).unwrap() } None => { srcs[srcs.len() - 2u].clone() } }; let mut actual = srcs[srcs.len() - 1u].clone(); if props.pp_exact.is_some() { // Now we have to care about line endings let cr = "\r".to_string(); actual = actual.replace(cr.as_slice(), "").to_string(); expected = expected.replace(cr.as_slice(), "").to_string(); } compare_source(expected.as_slice(), actual.as_slice()); // If we're only making sure that the output matches then just stop here if props.pretty_compare_only { return; } // Finally, let's make sure it actually appears to remain valid code let proc_res = typecheck_source(config, props, testfile, actual); if !proc_res.status.success() { fatal_proc_rec("pretty-printed source does not typecheck", &proc_res); } if props.no_pretty_expanded { return } // additionally, run `--pretty expanded` and try to build it. let proc_res = print_source(config, props, testfile, srcs[round].clone(), "expanded"); if !proc_res.status.success() { fatal_proc_rec("pretty-printing (expanded) failed", &proc_res); } let ProcRes{ stdout: expanded_src, .. } = proc_res; let proc_res = typecheck_source(config, props, testfile, expanded_src); if !proc_res.status.success() { fatal_proc_rec("pretty-printed source (expanded) does not typecheck", &proc_res); } return; fn print_source(config: &Config, props: &TestProps, testfile: &Path, src: String, pretty_type: &str) -> ProcRes { let aux_dir = aux_output_dir_name(config, testfile); compose_and_run(config, testfile, make_pp_args(config, props, testfile, pretty_type.to_string()), props.exec_env.clone(), config.compile_lib_path.as_slice(), Some(aux_dir.as_str().unwrap()), Some(src)) } fn make_pp_args(config: &Config, props: &TestProps, testfile: &Path, pretty_type: String) -> ProcArgs { let aux_dir = aux_output_dir_name(config, testfile); // FIXME (#9639): This needs to handle non-utf8 paths let mut args = vec!("-".to_string(), "-Zunstable-options".to_string(), "--pretty".to_string(), pretty_type, format!("--target={}", config.target), "-L".to_string(), aux_dir.as_str().unwrap().to_string()); args.extend(split_maybe_args(&config.target_rustcflags).into_iter()); args.extend(split_maybe_args(&props.compile_flags).into_iter()); return ProcArgs { prog: config.rustc_path.as_str().unwrap().to_string(), args: args, }; } fn compare_source(expected: &str, actual: &str) { if expected != actual { error("pretty-printed source does not match expected source"); println!("\n\ expected:\n\ ------------------------------------------\n\ {}\n\ ------------------------------------------\n\ actual:\n\ ------------------------------------------\n\ {}\n\ ------------------------------------------\n\ \n", expected, actual); panic!(); } } fn typecheck_source(config: &Config, props: &TestProps, testfile: &Path, src: String) -> 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 aux_dir = aux_output_dir_name(config, testfile); let target = if props.force_host { config.host.as_slice() } else { config.target.as_slice() }; // FIXME (#9639): This needs to handle non-utf8 paths let mut args = vec!("-".to_string(), "-Zno-trans".to_string(), "--crate-type=lib".to_string(), format!("--target={}", target), "-L".to_string(), config.build_base.as_str().unwrap().to_string(), "-L".to_string(), aux_dir.as_str().unwrap().to_string()); args.extend(split_maybe_args(&config.target_rustcflags).into_iter()); args.extend(split_maybe_args(&props.compile_flags).into_iter()); // FIXME (#9639): This needs to handle non-utf8 paths return ProcArgs { prog: config.rustc_path.as_str().unwrap().to_string(), args: args, }; } } fn run_debuginfo_gdb_test(config: &Config, props: &TestProps, testfile: &Path) { let mut config = Config { target_rustcflags: cleanup_debug_info_options(&config.target_rustcflags), host_rustcflags: cleanup_debug_info_options(&config.host_rustcflags), .. config.clone() }; let config = &mut config; let DebuggerCommands { commands, check_lines, breakpoint_lines } = parse_debugger_commands(testfile, "gdb"); let mut cmds = commands.connect("\n"); // compile test file (it should have 'compile-flags:-g' in the header) let compiler_run_result = compile_test(config, props, testfile); if !compiler_run_result.status.success() { fatal_proc_rec("compilation failed!", &compiler_run_result); } let exe_file = make_exe_name(config, testfile); let debugger_run_result; match config.target.as_slice() { "arm-linux-androideabi" => { cmds = cmds.replace("run", "continue").to_string(); // write debugger script let script_str = ["set charset UTF-8".to_string(), format!("file {}", exe_file.as_str().unwrap() .to_string()), "target remote :5039".to_string(), cmds, "quit".to_string()].connect("\n"); debug!("script_str = {}", script_str); dump_output_file(config, testfile, script_str.as_slice(), "debugger.script"); procsrv::run("", config.adb_path.as_slice(), None, &[ "push".to_string(), exe_file.as_str().unwrap().to_string(), config.adb_test_dir.clone() ], vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{:?}`", config.adb_path).as_slice()); procsrv::run("", config.adb_path.as_slice(), None, &[ "forward".to_string(), "tcp:5039".to_string(), "tcp:5039".to_string() ], vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{:?}`", config.adb_path).as_slice()); let adb_arg = format!("export LD_LIBRARY_PATH={}; \ gdbserver :5039 {}/{}", config.adb_test_dir.clone(), config.adb_test_dir.clone(), str::from_utf8( exe_file.filename() .unwrap()).unwrap()); let mut process = procsrv::run_background("", config.adb_path .as_slice(), None, &[ "shell".to_string(), adb_arg.clone() ], vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{:?}`", config.adb_path).as_slice()); loop { //waiting 1 second for gdbserver start timer::sleep(Duration::milliseconds(1000)); let result = Thread::scoped(move || { tcp::TcpStream::connect("127.0.0.1:5039").unwrap(); }).join(); if result.is_err() { continue; } break; } let tool_path = match config.android_cross_path.as_str() { Some(x) => x.to_string(), None => fatal("cannot find android cross path") }; let debugger_script = make_out_name(config, testfile, "debugger.script"); // FIXME (#9639): This needs to handle non-utf8 paths let debugger_opts = vec!("-quiet".to_string(), "-batch".to_string(), "-nx".to_string(), format!("-command={}", debugger_script.as_str().unwrap())); let mut gdb_path = tool_path; gdb_path.push_str("/bin/arm-linux-androideabi-gdb"); let procsrv::Result { out, err, status } = procsrv::run("", gdb_path.as_slice(), None, debugger_opts.as_slice(), vec!(("".to_string(), "".to_string())), None) .expect(format!("failed to exec `{:?}`", gdb_path).as_slice()); let cmdline = { let cmdline = make_cmdline("", "arm-linux-androideabi-gdb", debugger_opts.as_slice()); logv(config, format!("executing {}", cmdline)); cmdline }; debugger_run_result = ProcRes { status: status, stdout: out, stderr: err, cmdline: cmdline }; process.signal_kill().unwrap(); } _=> { let rust_src_root = find_rust_src_root(config) .expect("Could not find Rust source root"); let rust_pp_module_rel_path = Path::new("./src/etc"); let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path) .as_str() .unwrap() .to_string(); // write debugger script let mut script_str = String::with_capacity(2048); script_str.push_str("set charset UTF-8\n"); script_str.push_str("show version\n"); match config.gdb_version { Some(ref version) => { println!("NOTE: compiletest thinks it is using GDB version {}", version.as_slice()); if header::gdb_version_to_int(version.as_slice()) > header::gdb_version_to_int("7.4") { // Add the directory containing the pretty printers to // GDB's script auto loading safe path script_str.push_str( format!("add-auto-load-safe-path {}\n", rust_pp_module_abs_path.replace("\\", "\\\\").as_slice()) .as_slice()); } } _ => { println!("NOTE: compiletest does not know which version of \ GDB it is using"); } } // The following line actually doesn't have to do anything with // pretty printing, it just tells GDB to print values on one line: script_str.push_str("set print pretty off\n"); // Add the pretty printer directory to GDB's source-file search path script_str.push_str(&format!("directory {}\n", rust_pp_module_abs_path)[]); // Load the target executable script_str.push_str(&format!("file {}\n", exe_file.as_str().unwrap().replace("\\", "\\\\"))[]); // Add line breakpoints for line in breakpoint_lines.iter() { script_str.push_str(&format!("break '{}':{}\n", testfile.filename_display(), *line)[]); } script_str.push_str(cmds.as_slice()); script_str.push_str("quit\n"); debug!("script_str = {}", script_str); dump_output_file(config, testfile, script_str.as_slice(), "debugger.script"); // run debugger script with gdb #[cfg(windows)] fn debugger() -> String { "gdb.exe".to_string() } #[cfg(unix)] fn debugger() -> String { "gdb".to_string() } let debugger_script = make_out_name(config, testfile, "debugger.script"); // FIXME (#9639): This needs to handle non-utf8 paths let debugger_opts = vec!("-quiet".to_string(), "-batch".to_string(), "-nx".to_string(), format!("-command={}", debugger_script.as_str().unwrap())); let proc_args = ProcArgs { prog: debugger(), args: debugger_opts, }; let environment = vec![("PYTHONPATH".to_string(), rust_pp_module_abs_path)]; debugger_run_result = compose_and_run(config, testfile, proc_args, environment, config.run_lib_path.as_slice(), None, None); } } if !debugger_run_result.status.success() { fatal("gdb failed to execute"); } check_debugger_output(&debugger_run_result, check_lines.as_slice()); } fn find_rust_src_root(config: &Config) -> Option { let mut path = config.src_base.clone(); let path_postfix = Path::new("src/etc/lldb_batchmode.py"); while path.pop() { if path.join(&path_postfix).is_file() { return Some(path); } } return None; } fn run_debuginfo_lldb_test(config: &Config, props: &TestProps, testfile: &Path) { use std::old_io::process::{Command, ProcessOutput}; if config.lldb_python_dir.is_none() { fatal("Can't run LLDB test because LLDB's python path is not set."); } let mut config = Config { target_rustcflags: cleanup_debug_info_options(&config.target_rustcflags), host_rustcflags: cleanup_debug_info_options(&config.host_rustcflags), .. config.clone() }; let config = &mut config; // compile test file (it should have 'compile-flags:-g' in the header) let compile_result = compile_test(config, props, testfile); if !compile_result.status.success() { fatal_proc_rec("compilation failed!", &compile_result); } let exe_file = make_exe_name(config, testfile); match config.lldb_version { Some(ref version) => { println!("NOTE: compiletest thinks it is using LLDB version {}", version.as_slice()); } _ => { println!("NOTE: compiletest does not know which version of \ LLDB it is using"); } } // Parse debugger commands etc from test files let DebuggerCommands { commands, check_lines, breakpoint_lines, .. } = parse_debugger_commands(testfile, "lldb"); // Write debugger script: // We don't want to hang when calling `quit` while the process is still running let mut script_str = String::from_str("settings set auto-confirm true\n"); // Make LLDB emit its version, so we have it documented in the test output script_str.push_str("version\n"); // Switch LLDB into "Rust mode" let rust_src_root = find_rust_src_root(config) .expect("Could not find Rust source root"); let rust_pp_module_rel_path = Path::new("./src/etc/lldb_rust_formatters.py"); let rust_pp_module_abs_path = rust_src_root.join(rust_pp_module_rel_path) .as_str() .unwrap() .to_string(); script_str.push_str(&format!("command script import {}\n", &rust_pp_module_abs_path[])[]); script_str.push_str("type summary add --no-value "); script_str.push_str("--python-function lldb_rust_formatters.print_val "); script_str.push_str("-x \".*\" --category Rust\n"); script_str.push_str("type category enable Rust\n"); // Set breakpoints on every line that contains the string "#break" for line in breakpoint_lines.iter() { script_str.push_str(format!("breakpoint set --line {}\n", line).as_slice()); } // Append the other commands for line in commands.iter() { script_str.push_str(line.as_slice()); script_str.push_str("\n"); } // Finally, quit the debugger script_str.push_str("quit\n"); // Write the script into a file debug!("script_str = {}", script_str); dump_output_file(config, testfile, script_str.as_slice(), "debugger.script"); let debugger_script = make_out_name(config, testfile, "debugger.script"); // Let LLDB execute the script via lldb_batchmode.py let debugger_run_result = run_lldb(config, &exe_file, &debugger_script, &rust_src_root); if !debugger_run_result.status.success() { fatal_proc_rec("Error while running LLDB", &debugger_run_result); } check_debugger_output(&debugger_run_result, check_lines.as_slice()); fn run_lldb(config: &Config, test_executable: &Path, debugger_script: &Path, rust_src_root: &Path) -> ProcRes { // Prepare the lldb_batchmode which executes the debugger script let lldb_script_path = rust_src_root.join(Path::new("./src/etc/lldb_batchmode.py")); let mut cmd = Command::new("python"); cmd.arg(lldb_script_path) .arg(test_executable) .arg(debugger_script) .env_set_all(&[("PYTHONPATH", config.lldb_python_dir.clone().unwrap().as_slice())]); let (status, out, err) = match cmd.spawn() { Ok(process) => { let ProcessOutput { status, output, error } = process.wait_with_output().unwrap(); (status, String::from_utf8(output).unwrap(), String::from_utf8(error).unwrap()) }, Err(e) => { fatal(format!("Failed to setup Python process for \ LLDB script: {}", e).as_slice()) } }; dump_output(config, test_executable, out.as_slice(), err.as_slice()); return ProcRes { status: status, stdout: out, stderr: err, cmdline: format!("{:?}", cmd) }; } } struct DebuggerCommands { commands: Vec, check_lines: Vec, breakpoint_lines: Vec, } fn parse_debugger_commands(file_path: &Path, debugger_prefix: &str) -> DebuggerCommands { use std::old_io::{BufferedReader, File}; let command_directive = format!("{}-command", debugger_prefix); let check_directive = format!("{}-check", debugger_prefix); let mut breakpoint_lines = vec!(); let mut commands = vec!(); let mut check_lines = vec!(); let mut counter = 1; let mut reader = BufferedReader::new(File::open(file_path).unwrap()); for line in reader.lines() { match line { Ok(line) => { if line.contains("#break") { breakpoint_lines.push(counter); } header::parse_name_value_directive( line.as_slice(), command_directive.as_slice()).map(|cmd| { commands.push(cmd) }); header::parse_name_value_directive( line.as_slice(), check_directive.as_slice()).map(|cmd| { check_lines.push(cmd) }); } Err(e) => { fatal(format!("Error while parsing debugger commands: {}", e).as_slice()) } } counter += 1; } DebuggerCommands { commands: commands, check_lines: check_lines, breakpoint_lines: breakpoint_lines, } } fn cleanup_debug_info_options(options: &Option) -> Option { if options.is_none() { return None; } // Remove options that are either unwanted (-O) or may lead to duplicates due to RUSTFLAGS. let options_to_remove = [ "-O".to_string(), "-g".to_string(), "--debuginfo".to_string() ]; let new_options = split_maybe_args(options).into_iter() .filter(|x| !options_to_remove.contains(x)) .collect::>() .connect(" "); Some(new_options) } fn check_debugger_output(debugger_run_result: &ProcRes, check_lines: &[String]) { let num_check_lines = check_lines.len(); if num_check_lines > 0 { // Allow check lines to leave parts unspecified (e.g., uninitialized // bits in the wrong case of an enum) with the notation "[...]". let check_fragments: Vec> = check_lines.iter().map(|s| { s.as_slice() .trim() .split_str("[...]") .map(|x| x.to_string()) .collect() }).collect(); // check if each line in props.check_lines appears in the // output (in order) let mut i = 0u; for line in debugger_run_result.stdout.lines() { let mut rest = line.trim(); let mut first = true; let mut failed = false; for frag in check_fragments[i].iter() { let found = if first { if rest.starts_with(frag.as_slice()) { Some(0) } else { None } } else { rest.find_str(frag.as_slice()) }; match found { None => { failed = true; break; } Some(i) => { rest = &rest[(i + frag.len())..]; } } first = false; } if !failed && rest.len() == 0 { i += 1u; } if i == num_check_lines { // all lines checked break; } } if i != num_check_lines { fatal_proc_rec(format!("line not found in debugger output: {}", check_lines.get(i).unwrap()).as_slice(), debugger_run_result); } } } fn check_error_patterns(props: &TestProps, testfile: &Path, output_to_check: &str, proc_res: &ProcRes) { if props.error_patterns.is_empty() { fatal(format!("no error pattern specified in {:?}", testfile.display()).as_slice()); } let mut next_err_idx = 0u; let mut next_err_pat = &props.error_patterns[next_err_idx]; let mut done = false; for line in output_to_check.lines() { if line.contains(next_err_pat.as_slice()) { debug!("found error pattern {}", 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 = &props.error_patterns[next_err_idx..]; if missing_patterns.len() == 1u { fatal_proc_rec(format!("error pattern '{}' not found!", missing_patterns[0]).as_slice(), proc_res); } else { for pattern in missing_patterns.iter() { error(format!("error pattern '{}' not found!", *pattern).as_slice()); } fatal_proc_rec("multiple error patterns not found", proc_res); } } fn check_no_compiler_crash(proc_res: &ProcRes) { for line in proc_res.stderr.lines() { if line.starts_with("error: internal compiler error:") { fatal_proc_rec("compiler encountered internal error", proc_res); } } } fn check_forbid_output(props: &TestProps, output_to_check: &str, proc_res: &ProcRes) { for pat in props.forbid_output.iter() { if output_to_check.contains(pat.as_slice()) { fatal_proc_rec("forbidden pattern found in compiler output", proc_res); } } } fn check_expected_errors(expected_errors: Vec , testfile: &Path, proc_res: &ProcRes) { // true if we found the error in question let mut found_flags: Vec<_> = repeat(false).take(expected_errors.len()).collect(); if proc_res.status.success() { fatal("process did not return an error status"); } let prefixes = expected_errors.iter().map(|ee| { format!("{}:{}:", testfile.display(), ee.line) }).collect:: >(); #[cfg(windows)] fn prefix_matches( line : &str, prefix : &str ) -> bool { line.to_ascii_lowercase().starts_with(prefix.to_ascii_lowercase().as_slice()) } #[cfg(unix)] fn prefix_matches( line : &str, prefix : &str ) -> bool { line.starts_with( prefix ) } // A multi-line error will have followup lines which will always // start with one of these strings. fn continuation( line: &str) -> bool { line.starts_with(" expected") || line.starts_with(" found") || // 1234 // Should have 4 spaces: see issue 18946 line.starts_with("(") } // 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 line in proc_res.stderr.lines() { let mut was_expected = false; for (i, ee) in expected_errors.iter().enumerate() { if !found_flags[i] { debug!("prefix={} ee.kind={} ee.msg={} line={}", prefixes[i].as_slice(), ee.kind, ee.msg, line); if (prefix_matches(line, prefixes[i].as_slice()) || continuation(line)) && line.contains(ee.kind.as_slice()) && line.contains(ee.msg.as_slice()) { found_flags[i] = true; was_expected = true; break; } } } // ignore this msg which gets printed at the end if line.contains("aborting due to") { was_expected = true; } if !was_expected && is_compiler_error_or_warning(line) { fatal_proc_rec(format!("unexpected compiler error or warning: '{}'", line).as_slice(), proc_res); } } for (i, &flag) in found_flags.iter().enumerate() { if !flag { let ee = &expected_errors[i]; fatal_proc_rec(format!("expected {} on line {} not found: {}", ee.kind, ee.line, ee.msg).as_slice(), proc_res); } } } 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 = haystack[(*idx)..].find(needle); if opt.is_none() { return false; } *idx = opt.unwrap(); return true; } fn scan_char(haystack: &str, needle: char, idx: &mut uint) -> bool { if *idx >= haystack.len() { return false; } let range = haystack.char_range_at(*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 = haystack.char_range_at(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 = haystack.char_range_at(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: String, args: Vec, } struct ProcRes { status: ProcessExit, stdout: String, stderr: String, cmdline: String, } 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".to_string()]) } fn compile_test_(config: &Config, props: &TestProps, testfile: &Path, extra_args: &[String]) -> ProcRes { let aux_dir = aux_output_dir_name(config, testfile); // FIXME (#9639): This needs to handle non-utf8 paths let mut link_args = vec!("-L".to_string(), aux_dir.as_str().unwrap().to_string()); link_args.extend(extra_args.iter().map(|s| s.clone())); let args = make_compile_args(config, props, link_args, |a, b| TargetLocation::ThisFile(make_exe_name(a, b)), testfile); compose_and_run_compiler(config, props, testfile, args, None) } fn exec_compiled_test(config: &Config, props: &TestProps, testfile: &Path) -> ProcRes { let env = props.exec_env.clone(); match config.target.as_slice() { "arm-linux-androideabi" => { _arm_exec_compiled_test(config, props, testfile, env) } _=> { let aux_dir = aux_output_dir_name(config, testfile); compose_and_run(config, testfile, make_run_args(config, props, testfile), env, config.run_lib_path.as_slice(), Some(aux_dir.as_str().unwrap()), None) } } } fn compose_and_run_compiler( config: &Config, props: &TestProps, testfile: &Path, args: ProcArgs, input: Option) -> ProcRes { if !props.aux_builds.is_empty() { ensure_dir(&aux_output_dir_name(config, testfile)); } let aux_dir = aux_output_dir_name(config, testfile); // FIXME (#9639): This needs to handle non-utf8 paths let extra_link_args = vec!("-L".to_string(), aux_dir.as_str().unwrap().to_string()); for rel_ab in props.aux_builds.iter() { let abs_ab = config.aux_base.join(rel_ab.as_slice()); let aux_props = header::load_props(&abs_ab); let mut crate_type = if aux_props.no_prefer_dynamic { Vec::new() } else { vec!("--crate-type=dylib".to_string()) }; crate_type.extend(extra_link_args.clone().into_iter()); let aux_args = make_compile_args(config, &aux_props, crate_type, |a,b| { let f = make_lib_name(a, b, testfile); TargetLocation::ThisDirectory(f.dir_path()) }, &abs_ab); let auxres = compose_and_run(config, &abs_ab, aux_args, Vec::new(), config.compile_lib_path.as_slice(), Some(aux_dir.as_str().unwrap()), None); if !auxres.status.success() { fatal_proc_rec( format!("auxiliary build of {:?} failed to compile: ", abs_ab.display()).as_slice(), &auxres); } match config.target.as_slice() { "arm-linux-androideabi" => { _arm_push_aux_shared_library(config, testfile); } _ => {} } } compose_and_run(config, testfile, args, Vec::new(), config.compile_lib_path.as_slice(), Some(aux_dir.as_str().unwrap()), input) } fn ensure_dir(path: &Path) { if path.is_dir() { return; } fs::mkdir(path, old_io::USER_RWX).unwrap(); } fn compose_and_run(config: &Config, testfile: &Path, ProcArgs{ args, prog }: ProcArgs, procenv: Vec<(String, String)> , lib_path: &str, aux_path: Option<&str>, input: Option) -> ProcRes { return program_output(config, testfile, lib_path, prog, aux_path, args, procenv, input); } enum TargetLocation { ThisFile(Path), ThisDirectory(Path), } fn make_compile_args(config: &Config, props: &TestProps, extras: Vec , xform: F, testfile: &Path) -> ProcArgs where F: FnOnce(&Config, &Path) -> TargetLocation, { let xform_file = xform(config, testfile); let target = if props.force_host { config.host.as_slice() } else { config.target.as_slice() }; // FIXME (#9639): This needs to handle non-utf8 paths let mut args = vec!(testfile.as_str().unwrap().to_string(), "-L".to_string(), config.build_base.as_str().unwrap().to_string(), format!("--target={}", target)); args.push_all(extras.as_slice()); if !props.no_prefer_dynamic { args.push("-C".to_string()); args.push("prefer-dynamic".to_string()); } let path = match xform_file { TargetLocation::ThisFile(path) => { args.push("-o".to_string()); path } TargetLocation::ThisDirectory(path) => { args.push("--out-dir".to_string()); path } }; args.push(path.as_str().unwrap().to_string()); if props.force_host { args.extend(split_maybe_args(&config.host_rustcflags).into_iter()); } else { args.extend(split_maybe_args(&config.target_rustcflags).into_iter()); } args.extend(split_maybe_args(&props.compile_flags).into_iter()); return ProcArgs { prog: config.rustc_path.as_str().unwrap().to_string(), 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).join(&auxname) } fn make_exe_name(config: &Config, testfile: &Path) -> Path { let mut f = output_base_name(config, testfile); if !os::consts::EXE_SUFFIX.is_empty() { let mut fname = f.filename().unwrap().to_vec(); fname.extend(os::consts::EXE_SUFFIX.bytes()); f.set_filename(fname); } f } 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 mut args = split_maybe_args(&config.runtool); let exe_file = make_exe_name(config, testfile); // FIXME (#9639): This needs to handle non-utf8 paths args.push(exe_file.as_str().unwrap().to_string()); // Add the arguments in the run_flags directive args.extend(split_maybe_args(&props.run_flags).into_iter()); let prog = args.remove(0); return ProcArgs { prog: prog, args: args, }; } fn split_maybe_args(argstr: &Option) -> Vec { match *argstr { Some(ref s) => { s.as_slice() .split(' ') .filter_map(|s| { if s.chars().all(|c| c.is_whitespace()) { None } else { Some(s.to_string()) } }).collect() } None => Vec::new() } } fn program_output(config: &Config, testfile: &Path, lib_path: &str, prog: String, aux_path: Option<&str>, args: Vec, env: Vec<(String, String)>, input: Option) -> ProcRes { let cmdline = { let cmdline = make_cmdline(lib_path, prog.as_slice(), args.as_slice()); logv(config, format!("executing {}", cmdline)); cmdline }; let procsrv::Result { out, err, status } = procsrv::run(lib_path, prog.as_slice(), aux_path, args.as_slice(), env, input).expect(format!("failed to exec `{}`", prog).as_slice()); dump_output(config, testfile, out.as_slice(), err.as_slice()); return ProcRes { status: status, stdout: out, stderr: err, cmdline: cmdline, }; } // Linux and mac don't require adjusting the library search path #[cfg(unix)] fn make_cmdline(_libpath: &str, prog: &str, args: &[String]) -> String { format!("{} {}", prog, args.connect(" ")) } #[cfg(windows)] fn make_cmdline(libpath: &str, prog: &str, args: &[String]) -> String { // 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) -> String { format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path)) } format!("{} {} {}", lib_path_cmd_prefix(libpath), prog, args.connect(" ")) } 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); File::create(&outfile).write_all(out.as_bytes()).unwrap(); } fn make_out_name(config: &Config, testfile: &Path, extension: &str) -> Path { output_base_name(config, testfile).with_extension(extension) } fn aux_output_dir_name(config: &Config, testfile: &Path) -> Path { let f = output_base_name(config, testfile); let mut fname = f.filename().unwrap().to_vec(); fname.extend("libaux".bytes()); f.with_filename(fname) } fn output_testname(testfile: &Path) -> Path { Path::new(testfile.filestem().unwrap()) } fn output_base_name(config: &Config, testfile: &Path) -> Path { config.build_base .join(&output_testname(testfile)) .with_extension(config.stage_id.as_slice()) } fn maybe_dump_to_stdout(config: &Config, out: &str, err: &str) { if config.verbose { println!("------{}------------------------------", "stdout"); println!("{}", out); println!("------{}------------------------------", "stderr"); println!("{}", err); println!("------------------------------------------"); } } fn error(err: &str) { println!("\nerror: {}", err); } fn fatal(err: &str) -> ! { error(err); panic!(); } fn fatal_proc_rec(err: &str, proc_res: &ProcRes) -> ! { print!("\n\ error: {}\n\ status: {}\n\ command: {}\n\ stdout:\n\ ------------------------------------------\n\ {}\n\ ------------------------------------------\n\ stderr:\n\ ------------------------------------------\n\ {}\n\ ------------------------------------------\n\ \n", err, proc_res.status, proc_res.cmdline, proc_res.stdout, proc_res.stderr); panic!(); } fn _arm_exec_compiled_test(config: &Config, props: &TestProps, testfile: &Path, env: Vec<(String, String)>) -> ProcRes { let args = make_run_args(config, props, testfile); let cmdline = make_cmdline("", args.prog.as_slice(), args.args.as_slice()); // get bare program string let mut tvec: Vec = args.prog .as_slice() .split('/') .map(|ts| ts.to_string()) .collect(); let prog_short = tvec.pop().unwrap(); // copy to target let copy_result = procsrv::run("", config.adb_path.as_slice(), None, &[ "push".to_string(), args.prog.clone(), config.adb_test_dir.clone() ], vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); if config.verbose { println!("push ({}) {} {} {}", config.target, args.prog, copy_result.out, copy_result.err); } logv(config, format!("executing ({}) {}", config.target, cmdline)); let mut runargs = Vec::new(); // run test via adb_run_wrapper runargs.push("shell".to_string()); for (key, val) in env.into_iter() { runargs.push(format!("{}={}", key, val)); } runargs.push(format!("{}/adb_run_wrapper.sh", config.adb_test_dir)); runargs.push(format!("{}", config.adb_test_dir)); runargs.push(format!("{}", prog_short)); for tv in args.args.iter() { runargs.push(tv.to_string()); } procsrv::run("", config.adb_path.as_slice(), None, runargs.as_slice(), vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); // get exitcode of result runargs = Vec::new(); runargs.push("shell".to_string()); runargs.push("cat".to_string()); runargs.push(format!("{}/{}.exitcode", config.adb_test_dir, prog_short)); let procsrv::Result{ out: exitcode_out, err: _, status: _ } = procsrv::run("", config.adb_path.as_slice(), None, runargs.as_slice(), vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); let mut exitcode: int = 0; for c in exitcode_out.chars() { if !c.is_numeric() { break; } exitcode = exitcode * 10 + match c { '0' ... '9' => c as int - ('0' as int), _ => 101, } } // get stdout of result runargs = Vec::new(); runargs.push("shell".to_string()); runargs.push("cat".to_string()); runargs.push(format!("{}/{}.stdout", config.adb_test_dir, prog_short)); let procsrv::Result{ out: stdout_out, err: _, status: _ } = procsrv::run("", config.adb_path.as_slice(), None, runargs.as_slice(), vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); // get stderr of result runargs = Vec::new(); runargs.push("shell".to_string()); runargs.push("cat".to_string()); runargs.push(format!("{}/{}.stderr", config.adb_test_dir, prog_short)); let procsrv::Result{ out: stderr_out, err: _, status: _ } = procsrv::run("", config.adb_path.as_slice(), None, runargs.as_slice(), vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); dump_output(config, testfile, stdout_out.as_slice(), stderr_out.as_slice()); ProcRes { status: process::ProcessExit::ExitStatus(exitcode), stdout: stdout_out, stderr: stderr_out, cmdline: cmdline } } fn _arm_push_aux_shared_library(config: &Config, testfile: &Path) { let tdir = aux_output_dir_name(config, testfile); let dirs = fs::readdir(&tdir).unwrap(); for file in dirs.iter() { if file.extension_str() == Some("so") { // FIXME (#9639): This needs to handle non-utf8 paths let copy_result = procsrv::run("", config.adb_path.as_slice(), None, &[ "push".to_string(), file.as_str() .unwrap() .to_string(), config.adb_test_dir.to_string() ], vec!(("".to_string(), "".to_string())), Some("".to_string())) .expect(format!("failed to exec `{}`", config.adb_path).as_slice()); if config.verbose { println!("push ({}) {:?} {} {}", config.target, file.display(), copy_result.out, copy_result.err); } } } } // codegen tests (vs. clang) fn append_suffix_to_stem(p: &Path, suffix: &str) -> Path { if suffix.len() == 0 { (*p).clone() } else { let mut stem = p.filestem().unwrap().to_vec(); stem.extend("-".bytes()); stem.extend(suffix.bytes()); p.with_filename(stem) } } fn compile_test_and_save_bitcode(config: &Config, props: &TestProps, testfile: &Path) -> ProcRes { let aux_dir = aux_output_dir_name(config, testfile); // FIXME (#9639): This needs to handle non-utf8 paths let mut link_args = vec!("-L".to_string(), aux_dir.as_str().unwrap().to_string()); let llvm_args = vec!("--emit=llvm-bc,obj".to_string(), "--crate-type=lib".to_string()); link_args.extend(llvm_args.into_iter()); let args = make_compile_args(config, props, link_args, |a, b| TargetLocation::ThisDirectory( output_base_name(a, b).dir_path()), testfile); compose_and_run_compiler(config, props, testfile, args, None) } fn compile_cc_with_clang_and_save_bitcode(config: &Config, _props: &TestProps, testfile: &Path) -> ProcRes { let bitcodefile = output_base_name(config, testfile).with_extension("bc"); let bitcodefile = append_suffix_to_stem(&bitcodefile, "clang"); let testcc = testfile.with_extension("cc"); let proc_args = ProcArgs { // FIXME (#9639): This needs to handle non-utf8 paths prog: config.clang_path.as_ref().unwrap().as_str().unwrap().to_string(), args: vec!("-c".to_string(), "-emit-llvm".to_string(), "-o".to_string(), bitcodefile.as_str().unwrap().to_string(), testcc.as_str().unwrap().to_string()) }; compose_and_run(config, testfile, proc_args, Vec::new(), "", None, None) } fn extract_function_from_bitcode(config: &Config, _props: &TestProps, fname: &str, testfile: &Path, suffix: &str) -> ProcRes { let bitcodefile = output_base_name(config, testfile).with_extension("bc"); let bitcodefile = append_suffix_to_stem(&bitcodefile, suffix); let extracted_bc = append_suffix_to_stem(&bitcodefile, "extract"); let prog = config.llvm_bin_path.as_ref().unwrap().join("llvm-extract"); let proc_args = ProcArgs { // FIXME (#9639): This needs to handle non-utf8 paths prog: prog.as_str().unwrap().to_string(), args: vec!(format!("-func={}", fname), format!("-o={}", extracted_bc.as_str().unwrap()), bitcodefile.as_str().unwrap().to_string()) }; compose_and_run(config, testfile, proc_args, Vec::new(), "", None, None) } fn disassemble_extract(config: &Config, _props: &TestProps, testfile: &Path, suffix: &str) -> ProcRes { let bitcodefile = output_base_name(config, testfile).with_extension("bc"); let bitcodefile = append_suffix_to_stem(&bitcodefile, suffix); let extracted_bc = append_suffix_to_stem(&bitcodefile, "extract"); let extracted_ll = extracted_bc.with_extension("ll"); let prog = config.llvm_bin_path.as_ref().unwrap().join("llvm-dis"); let proc_args = ProcArgs { // FIXME (#9639): This needs to handle non-utf8 paths prog: prog.as_str().unwrap().to_string(), args: vec!(format!("-o={}", extracted_ll.as_str().unwrap()), extracted_bc.as_str().unwrap().to_string()) }; compose_and_run(config, testfile, proc_args, Vec::new(), "", None, None) } fn count_extracted_lines(p: &Path) -> uint { let x = File::open(&p.with_extension("ll")).read_to_end().unwrap(); let x = str::from_utf8(x.as_slice()).unwrap(); x.lines().count() } fn run_codegen_test(config: &Config, props: &TestProps, testfile: &Path, mm: &mut MetricMap) { if config.llvm_bin_path.is_none() { fatal("missing --llvm-bin-path"); } if config.clang_path.is_none() { fatal("missing --clang-path"); } let mut proc_res = compile_test_and_save_bitcode(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } proc_res = extract_function_from_bitcode(config, props, "test", testfile, ""); if !proc_res.status.success() { fatal_proc_rec("extracting 'test' function failed", &proc_res); } proc_res = disassemble_extract(config, props, testfile, ""); if !proc_res.status.success() { fatal_proc_rec("disassembling extract failed", &proc_res); } let mut proc_res = compile_cc_with_clang_and_save_bitcode(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } proc_res = extract_function_from_bitcode(config, props, "test", testfile, "clang"); if !proc_res.status.success() { fatal_proc_rec("extracting 'test' function failed", &proc_res); } proc_res = disassemble_extract(config, props, testfile, "clang"); if !proc_res.status.success() { fatal_proc_rec("disassembling extract failed", &proc_res); } let base = output_base_name(config, testfile); let base_extract = append_suffix_to_stem(&base, "extract"); let base_clang = append_suffix_to_stem(&base, "clang"); let base_clang_extract = append_suffix_to_stem(&base_clang, "extract"); let base_lines = count_extracted_lines(&base_extract); let clang_lines = count_extracted_lines(&base_clang_extract); mm.insert_metric("clang-codegen-ratio", (base_lines as f64) / (clang_lines as f64), 0.001); }