// 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, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind}; use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc}; use errors; use header::TestProps; use header; use procsrv; use util::logv; use std::env; use std::fmt; use std::fs::{self, File}; use std::io::BufReader; use std::io::prelude::*; use std::net::TcpStream; use std::path::{Path, PathBuf, Component}; use std::process::{Command, Output, ExitStatus}; pub fn run(config: Config, testfile: &Path) { match &*config.target { "arm-linux-androideabi" | "aarch64-linux-android" => { if !config.adb_device_status { panic!("android device not available"); } } _=> { } } if config.verbose { // We're going to be dumping a lot of info. Start on a new line. print!("\n\n"); } debug!("running {:?}", testfile.display()); let props = header::load_props(&testfile); debug!("loaded props"); match config.mode { CompileFail => run_cfail_test(&config, &props, &testfile), ParseFail => 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), Rustdoc => run_rustdoc_test(&config, &props, &testfile), } } 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(&format!("{} test compiled successfully!", config.mode)[..], &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, &proc_res); } check_no_compiler_crash(&proc_res); check_forbid_output(props, &output_to_check, &proc_res); } fn run_rfail_test(config: &Config, props: &TestProps, testfile: &Path) { let proc_res = compile_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } let proc_res = exec_compiled_test(config, props, testfile); // The value our Makefile configures valgrind to return on failure const VALGRIND_ERR: i32 = 100; if proc_res.status.code() == Some(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, &proc_res); } fn check_correct_failure_status(proc_res: &ProcRes) { // The value the rust runtime returns on failure const RUST_ERR: i32 = 101; if proc_res.status.code() != Some(RUST_ERR) { fatal_proc_rec( &format!("failure produced the wrong error: {}", proc_res.status), proc_res); } } fn run_rpass_test(config: &Config, props: &TestProps, testfile: &Path) { let proc_res = compile_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } let proc_res = exec_compiled_test(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("test run 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_owned()); } else { logv(config, "testing for converging pretty-printing".to_owned()); } let rounds = match props.pp_exact { Some(_) => 1, None => 2 }; let mut src = String::new(); File::open(testfile).unwrap().read_to_string(&mut src).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_owned(), &props.pretty_mode); if !proc_res.status.success() { fatal_proc_rec(&format!("pretty-printing failed in round {}", round), &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.parent().unwrap().join(file); let mut s = String::new(); File::open(&filepath).unwrap().read_to_string(&mut s).unwrap(); s } None => { srcs[srcs.len() - 2].clone() } }; let mut actual = srcs[srcs.len() - 1].clone(); if props.pp_exact.is_some() { // Now we have to care about line endings let cr = "\r".to_owned(); actual = actual.replace(&cr, "").to_owned(); expected = expected.replace(&cr, "").to_owned(); } compare_source(&expected, &actual); // 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.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_owned()), props.exec_env.clone(), &config.compile_lib_path, Some(aux_dir.to_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_owned(), "-Zunstable-options".to_owned(), "--unpretty".to_owned(), pretty_type, format!("--target={}", config.target), "-L".to_owned(), aux_dir.to_str().unwrap().to_owned()); args.extend(split_maybe_args(&config.target_rustcflags)); args.extend(split_maybe_args(&props.compile_flags)); return ProcArgs { prog: config.rustc_path.to_str().unwrap().to_owned(), 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 } else { &*config.target }; // FIXME (#9639): This needs to handle non-utf8 paths let mut args = vec!("-".to_owned(), "-Zno-trans".to_owned(), format!("--target={}", target), "-L".to_owned(), config.build_base.to_str().unwrap().to_owned(), "-L".to_owned(), aux_dir.to_str().unwrap().to_owned()); args.extend(split_maybe_args(&config.target_rustcflags)); args.extend(split_maybe_args(&props.compile_flags)); // FIXME (#9639): This needs to handle non-utf8 paths return ProcArgs { prog: config.rustc_path.to_str().unwrap().to_owned(), 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.join("\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 { "arm-linux-androideabi" | "aarch64-linux-android" => { cmds = cmds.replace("run", "continue"); // write debugger script let mut script_str = String::with_capacity(2048); script_str.push_str(&format!("set charset {}\n", charset())); script_str.push_str(&format!("file {}\n", exe_file.to_str().unwrap())); script_str.push_str("target remote :5039\n"); script_str.push_str(&format!("set solib-search-path \ ./{}/stage2/lib/rustlib/{}/lib/\n", config.host, config.target)); for line in &breakpoint_lines { script_str.push_str(&format!("break {:?}:{}\n", testfile.file_name().unwrap() .to_string_lossy(), *line)[..]); } script_str.push_str(&cmds); script_str.push_str("\nquit\n"); debug!("script_str = {}", script_str); dump_output_file(config, testfile, &script_str, "debugger.script"); procsrv::run("", &config.adb_path, None, &[ "push".to_owned(), exe_file.to_str().unwrap().to_owned(), config.adb_test_dir.clone() ], vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{:?}`", config.adb_path)); procsrv::run("", &config.adb_path, None, &[ "forward".to_owned(), "tcp:5039".to_owned(), "tcp:5039".to_owned() ], vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{:?}`", config.adb_path)); let adb_arg = format!("export LD_LIBRARY_PATH={}; \ gdbserver{} :5039 {}/{}", config.adb_test_dir.clone(), if config.target.contains("aarch64") {"64"} else {""}, config.adb_test_dir.clone(), exe_file.file_name().unwrap().to_str() .unwrap()); let mut process = procsrv::run_background("", &config.adb_path , None, &[ "shell".to_owned(), adb_arg.clone() ], vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{:?}`", config.adb_path)); loop { //waiting 1 second for gdbserver start ::std::thread::sleep_ms(1000); if TcpStream::connect("127.0.0.1:5039").is_ok() { break } } let tool_path = match config.android_cross_path.to_str() { Some(x) => x.to_owned(), 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_owned(), "-batch".to_owned(), "-nx".to_owned(), format!("-command={}", debugger_script.to_str().unwrap())); let mut gdb_path = tool_path; gdb_path.push_str(&format!("/bin/{}-gdb", config.target)); let procsrv::Result { out, err, status } = procsrv::run("", &gdb_path, None, &debugger_opts, vec!(("".to_owned(), "".to_owned())), None) .expect(&format!("failed to exec `{:?}`", gdb_path)); let cmdline = { let cmdline = make_cmdline("", &format!("{}-gdb", config.target), &debugger_opts); logv(config, format!("executing {}", cmdline)); cmdline }; debugger_run_result = ProcRes { status: Status::Normal(status), stdout: out, stderr: err, cmdline: cmdline }; if process.kill().is_err() { println!("Adb process is already finished."); } } _=> { 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) .to_str() .unwrap() .to_owned(); // write debugger script let mut script_str = String::with_capacity(2048); script_str.push_str(&format!("set charset {}\n", charset())); script_str.push_str("show version\n"); match config.gdb_version { Some(ref version) => { println!("NOTE: compiletest thinks it is using GDB version {}", version); if header::gdb_version_to_int(version) > 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(r"\", r"\\")) ); } } _ => { 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.to_str().unwrap() .replace(r"\", r"\\"))); // Add line breakpoints for line in &breakpoint_lines { script_str.push_str(&format!("break '{}':{}\n", testfile.file_name().unwrap() .to_string_lossy(), *line)); } script_str.push_str(&cmds); script_str.push_str("\nquit\n"); debug!("script_str = {}", script_str); dump_output_file(config, testfile, &script_str, "debugger.script"); // run debugger script with gdb fn debugger() -> &'static str { if cfg!(windows) {"gdb.exe"} else {"gdb"} } 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_owned(), "-batch".to_owned(), "-nx".to_owned(), format!("-command={}", debugger_script.to_str().unwrap())); let proc_args = ProcArgs { prog: debugger().to_owned(), args: debugger_opts, }; let environment = vec![("PYTHONPATH".to_owned(), rust_pp_module_abs_path)]; debugger_run_result = compose_and_run(config, testfile, proc_args, environment, &config.run_lib_path, None, None); } } if !debugger_run_result.status.success() { fatal("gdb failed to execute"); } check_debugger_output(&debugger_run_result, &check_lines); } 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) { 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); } _ => { 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("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) .to_str() .unwrap() .to_owned(); 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 { script_str.push_str(&format!("breakpoint set --line {}\n", line)); } // Append the other commands for line in &commands { script_str.push_str(line); script_str.push_str("\n"); } // Finally, quit the debugger script_str.push_str("\nquit\n"); // Write the script into a file debug!("script_str = {}", script_str); dump_output_file(config, testfile, &script_str, "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); 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("src/etc/lldb_batchmode.py"); cmd2procres(config, test_executable, Command::new(&config.python) .arg(&lldb_script_path) .arg(test_executable) .arg(debugger_script) .env("PYTHONPATH", config.lldb_python_dir.as_ref().unwrap())) } } fn cmd2procres(config: &Config, test_executable: &Path, cmd: &mut Command) -> ProcRes { let (status, out, err) = match cmd.output() { Ok(Output { status, stdout, stderr }) => { (status, String::from_utf8(stdout).unwrap(), String::from_utf8(stderr).unwrap()) }, Err(e) => { fatal(&format!("Failed to setup Python process for \ LLDB script: {}", e)) } }; dump_output(config, test_executable, &out, &err); ProcRes { status: Status::Normal(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 { 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 reader = BufReader::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, &command_directive).map(|cmd| { commands.push(cmd) }); header::parse_name_value_directive( &line, &check_directive).map(|cmd| { check_lines.push(cmd) }); } Err(e) => { fatal(&format!("Error while parsing debugger commands: {}", e)) } } 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_owned(), "-g".to_owned(), "--debuginfo".to_owned() ]; let new_options = split_maybe_args(options).into_iter() .filter(|x| !options_to_remove.contains(x)) .collect::>() .join(" "); 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 .trim() .split("[...]") .map(str::to_owned) .collect() }).collect(); // check if each line in props.check_lines appears in the // output (in order) let mut i = 0; 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] { let found = if first { if rest.starts_with(frag) { Some(0) } else { None } } else { rest.find(frag) }; match found { None => { failed = true; break; } Some(i) => { rest = &rest[(i + frag.len())..]; } } first = false; } if !failed && rest.is_empty() { i += 1; } 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()), 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())); } let mut next_err_idx = 0; 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) { debug!("found error pattern {}", next_err_pat); next_err_idx += 1; 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() == 1 { fatal_proc_rec(&format!("error pattern '{}' not found!", missing_patterns[0]), proc_res); } else { for pattern in missing_patterns { error(&format!("error pattern '{}' not found!", *pattern)); } 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 { if output_to_check.contains(pat) { 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![false; expected_errors.len()]; 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::>(); fn prefix_matches(line: &str, prefix: &str) -> bool { use std::ascii::AsciiExt; // On windows just translate all '\' path separators to '/' let line = line.replace(r"\", "/"); if cfg!(windows) { line.to_ascii_lowercase().starts_with(&prefix.to_ascii_lowercase()) } else { line.starts_with(prefix) } } // A multi-line error will have followup lines which start with a space // or open paren. fn continuation( line: &str) -> bool { line.starts_with(" ") || 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. // // This pattern is ambiguous on windows, because filename may contain // a colon, so any path prefix must be detected and removed first. for line in proc_res.stderr.lines() { let mut was_expected = false; let mut prev = 0; for (i, ee) in expected_errors.iter().enumerate() { if !found_flags[i] { debug!("prefix={} ee.kind={} ee.msg={} line={}", prefixes[i], ee.kind, ee.msg, line); // Suggestions have no line number in their output, so take on the line number of // the previous expected error if ee.kind == "suggestion" { assert!(expected_errors[prev].kind == "help", "SUGGESTIONs must be preceded by a HELP"); if line.contains(&ee.msg) { found_flags[i] = true; was_expected = true; break; } } if (prefix_matches(line, &prefixes[i]) || continuation(line)) && line.contains(&ee.kind) && line.contains(&ee.msg) { found_flags[i] = true; was_expected = true; break; } } prev = i; } // 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), 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), proc_res); } } } fn is_compiler_error_or_warning(mut line: &str) -> bool { // Remove initial prefix which may contain a colon let mut components = Path::new(line).components(); if let Some(Component::Prefix(_)) = components.peek() { components.next(); } // Safe as path was originally constructed from a &str ^ line = components.as_path().to_str().unwrap(); let mut i = 0; 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 usize) -> 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 usize) -> bool { if *idx >= haystack.len() { return false; } let ch = haystack.char_at(*idx); if ch != needle { return false; } *idx += ch.len_utf8(); return true; } fn scan_integer(haystack: &str, idx: &mut usize) -> bool { let mut i = *idx; while i < haystack.len() { let ch = haystack.char_at(i); if ch < '0' || '9' < ch { break; } i += ch.len_utf8(); } if i == *idx { return false; } *idx = i; return true; } fn scan_string(haystack: &str, needle: &str, idx: &mut usize) -> bool { let mut haystack_i = *idx; let mut needle_i = 0; while needle_i < needle.len() { if haystack_i >= haystack.len() { return false; } let ch = haystack.char_at(haystack_i); haystack_i += ch.len_utf8(); if !scan_char(needle, ch, &mut needle_i) { return false; } } *idx = haystack_i; return true; } struct ProcArgs { prog: String, args: Vec, } struct ProcRes { status: Status, stdout: String, stderr: String, cmdline: String, } enum Status { Parsed(i32), Normal(ExitStatus), } impl Status { fn code(&self) -> Option { match *self { Status::Parsed(i) => Some(i), Status::Normal(ref e) => e.code(), } } fn success(&self) -> bool { match *self { Status::Parsed(i) => i == 0, Status::Normal(ref e) => e.success(), } } } impl fmt::Display for Status { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Status::Parsed(i) => write!(f, "exit code: {}", i), Status::Normal(ref e) => e.fmt(f), } } } fn compile_test(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 link_args = vec!("-L".to_owned(), aux_dir.to_str().unwrap().to_owned()); 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 document(config: &Config, props: &TestProps, testfile: &Path) -> (ProcRes, PathBuf) { let aux_dir = aux_output_dir_name(config, testfile); let out_dir = output_base_name(config, testfile); let _ = fs::remove_dir_all(&out_dir); ensure_dir(&out_dir); let mut args = vec!["-L".to_owned(), aux_dir.to_str().unwrap().to_owned(), "-o".to_owned(), out_dir.to_str().unwrap().to_owned(), testfile.to_str().unwrap().to_owned()]; args.extend(split_maybe_args(&props.compile_flags)); let args = ProcArgs { prog: config.rustdoc_path.to_str().unwrap().to_owned(), args: args, }; (compose_and_run_compiler(config, props, testfile, args, None), out_dir) } fn exec_compiled_test(config: &Config, props: &TestProps, testfile: &Path) -> ProcRes { let env = props.exec_env.clone(); match &*config.target { "arm-linux-androideabi" | "aarch64-linux-android" => { _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, Some(aux_dir.to_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_owned(), aux_dir.to_str().unwrap().to_owned()]; for rel_ab in &props.aux_builds { let abs_ab = config.aux_base.join(rel_ab); let aux_props = header::load_props(&abs_ab); let mut crate_type = if aux_props.no_prefer_dynamic { Vec::new() } else { // We primarily compile all auxiliary libraries as dynamic libraries // to avoid code size bloat and large binaries as much as possible // for the test suite (otherwise including libstd statically in all // executables takes up quite a bit of space). // // For targets like MUSL, however, there is no support for dynamic // libraries so we just go back to building a normal library. Note, // however, that if the library is built with `force_host` then it's // ok to be a dylib as the host should always support dylibs. if config.target.contains("musl") && !aux_props.force_host { vec!("--crate-type=lib".to_owned()) } else { vec!("--crate-type=dylib".to_owned()) } }; crate_type.extend(extra_link_args.clone()); let aux_args = make_compile_args(config, &aux_props, crate_type, |a,b| { let f = make_lib_name(a, b, testfile); let parent = f.parent().unwrap(); TargetLocation::ThisDirectory(parent.to_path_buf()) }, &abs_ab); let auxres = compose_and_run(config, &abs_ab, aux_args, Vec::new(), &config.compile_lib_path, Some(aux_dir.to_str().unwrap()), None); if !auxres.status.success() { fatal_proc_rec( &format!("auxiliary build of {:?} failed to compile: ", abs_ab.display()), &auxres); } match &*config.target { "arm-linux-androideabi" | "aarch64-linux-android" => { _arm_push_aux_shared_library(config, testfile); } _ => {} } } compose_and_run(config, testfile, args, Vec::new(), &config.compile_lib_path, Some(aux_dir.to_str().unwrap()), input) } fn ensure_dir(path: &Path) { if path.is_dir() { return; } fs::create_dir(path).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(PathBuf), ThisDirectory(PathBuf), } 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 } else { &*config.target }; // FIXME (#9639): This needs to handle non-utf8 paths let mut args = vec!(testfile.to_str().unwrap().to_owned(), "-L".to_owned(), config.build_base.to_str().unwrap().to_owned(), format!("--target={}", target)); args.push_all(&extras); if !props.no_prefer_dynamic { args.push("-C".to_owned()); args.push("prefer-dynamic".to_owned()); } let path = match xform_file { TargetLocation::ThisFile(path) => { args.push("-o".to_owned()); path } TargetLocation::ThisDirectory(path) => { args.push("--out-dir".to_owned()); path } }; args.push(path.to_str().unwrap().to_owned()); if props.force_host { args.extend(split_maybe_args(&config.host_rustcflags)); } else { args.extend(split_maybe_args(&config.target_rustcflags)); } args.extend(split_maybe_args(&props.compile_flags)); return ProcArgs { prog: config.rustc_path.to_str().unwrap().to_owned(), args: args, }; } fn make_lib_name(config: &Config, auxfile: &Path, testfile: &Path) -> PathBuf { // 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) -> PathBuf { let mut f = output_base_name(config, testfile); if !env::consts::EXE_SUFFIX.is_empty() { let mut fname = f.file_name().unwrap().to_os_string(); fname.push(env::consts::EXE_SUFFIX); f.set_file_name(&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.to_str().unwrap().to_owned()); // Add the arguments in the run_flags directive args.extend(split_maybe_args(&props.run_flags)); let prog = args.remove(0); return ProcArgs { prog: prog, args: args, }; } fn split_maybe_args(argstr: &Option) -> Vec { match *argstr { Some(ref s) => { s .split(' ') .filter_map(|s| { if s.chars().all(|c| c.is_whitespace()) { None } else { Some(s.to_owned()) } }).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, &args); logv(config, format!("executing {}", cmdline)); cmdline }; let procsrv::Result { out, err, status } = procsrv::run(lib_path, &prog, aux_path, &args, env, input).expect(&format!("failed to exec `{}`", prog)); dump_output(config, testfile, &out, &err); return ProcRes { status: Status::Normal(status), stdout: out, stderr: err, cmdline: cmdline, }; } fn make_cmdline(libpath: &str, prog: &str, args: &[String]) -> String { use util; // Linux and mac don't require adjusting the library search path if cfg!(unix) { format!("{} {}", prog, args.join(" ")) } else { // 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.join(" ")) } } 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).unwrap().write_all(out.as_bytes()).unwrap(); } fn make_out_name(config: &Config, testfile: &Path, extension: &str) -> PathBuf { output_base_name(config, testfile).with_extension(extension) } fn aux_output_dir_name(config: &Config, testfile: &Path) -> PathBuf { let f = output_base_name(config, testfile); let mut fname = f.file_name().unwrap().to_os_string(); fname.push(&format!(".{}.libaux", config.mode)); f.with_file_name(&fname) } fn output_testname(testfile: &Path) -> PathBuf { PathBuf::from(testfile.file_stem().unwrap()) } fn output_base_name(config: &Config, testfile: &Path) -> PathBuf { config.build_base .join(&output_testname(testfile)) .with_extension(&config.stage_id) } 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, &args.args); // get bare program string let mut tvec: Vec = args.prog .split('/') .map(str::to_owned) .collect(); let prog_short = tvec.pop().unwrap(); // copy to target let copy_result = procsrv::run("", &config.adb_path, None, &[ "push".to_owned(), args.prog.clone(), config.adb_test_dir.clone() ], vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); 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_owned()); for (key, val) in env { 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 { runargs.push(tv.to_owned()); } procsrv::run("", &config.adb_path, None, &runargs, vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); // get exitcode of result runargs = Vec::new(); runargs.push("shell".to_owned()); runargs.push("cat".to_owned()); runargs.push(format!("{}/{}.exitcode", config.adb_test_dir, prog_short)); let procsrv::Result{ out: exitcode_out, err: _, status: _ } = procsrv::run("", &config.adb_path, None, &runargs, vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); let mut exitcode: i32 = 0; for c in exitcode_out.chars() { if !c.is_numeric() { break; } exitcode = exitcode * 10 + match c { '0' ... '9' => c as i32 - ('0' as i32), _ => 101, } } // get stdout of result runargs = Vec::new(); runargs.push("shell".to_owned()); runargs.push("cat".to_owned()); runargs.push(format!("{}/{}.stdout", config.adb_test_dir, prog_short)); let procsrv::Result{ out: stdout_out, err: _, status: _ } = procsrv::run("", &config.adb_path, None, &runargs, vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); // get stderr of result runargs = Vec::new(); runargs.push("shell".to_owned()); runargs.push("cat".to_owned()); runargs.push(format!("{}/{}.stderr", config.adb_test_dir, prog_short)); let procsrv::Result{ out: stderr_out, err: _, status: _ } = procsrv::run("", &config.adb_path, None, &runargs, vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); dump_output(config, testfile, &stdout_out, &stderr_out); ProcRes { status: Status::Parsed(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::read_dir(&tdir).unwrap(); for file in dirs { let file = file.unwrap().path(); if file.extension().and_then(|s| s.to_str()) == Some("so") { // FIXME (#9639): This needs to handle non-utf8 paths let copy_result = procsrv::run("", &config.adb_path, None, &[ "push".to_owned(), file.to_str() .unwrap() .to_owned(), config.adb_test_dir.to_owned(), ], vec!(("".to_owned(), "".to_owned())), Some("".to_owned())) .expect(&format!("failed to exec `{}`", config.adb_path)); if config.verbose { println!("push ({}) {:?} {} {}", config.target, file.display(), copy_result.out, copy_result.err); } } } } // codegen tests (using FileCheck) fn compile_test_and_save_ir(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_owned(), aux_dir.to_str().unwrap().to_owned()); let llvm_args = vec!("--emit=llvm-ir".to_owned(), "--crate-type=lib".to_owned()); link_args.extend(llvm_args); let args = make_compile_args(config, props, link_args, |a, b| TargetLocation::ThisDirectory( output_base_name(a, b).parent() .unwrap().to_path_buf()), testfile); compose_and_run_compiler(config, props, testfile, args, None) } fn check_ir_with_filecheck(config: &Config, testfile: &Path) -> ProcRes { let irfile = output_base_name(config, testfile).with_extension("ll"); let prog = config.llvm_bin_path.as_ref().unwrap().join("FileCheck"); let proc_args = ProcArgs { // FIXME (#9639): This needs to handle non-utf8 paths prog: prog.to_str().unwrap().to_owned(), args: vec!(format!("-input-file={}", irfile.to_str().unwrap()), testfile.to_str().unwrap().to_owned()) }; compose_and_run(config, testfile, proc_args, Vec::new(), "", None, None) } fn run_codegen_test(config: &Config, props: &TestProps, testfile: &Path) { if config.llvm_bin_path.is_none() { fatal("missing --llvm-bin-path"); } let mut proc_res = compile_test_and_save_ir(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("compilation failed!", &proc_res); } proc_res = check_ir_with_filecheck(config, testfile); if !proc_res.status.success() { fatal_proc_rec("verification with 'FileCheck' failed", &proc_res); } } fn charset() -> &'static str { // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset if cfg!(target_os = "bitrig") { "auto" } else if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" } } fn run_rustdoc_test(config: &Config, props: &TestProps, testfile: &Path) { let (proc_res, out_dir) = document(config, props, testfile); if !proc_res.status.success() { fatal_proc_rec("rustdoc failed!", &proc_res); } let root = find_rust_src_root(config).unwrap(); let res = cmd2procres(config, testfile, Command::new(&config.python) .arg(root.join("src/etc/htmldocck.py")) .arg(out_dir) .arg(testfile)); if !res.status.success() { fatal_proc_rec("htmldocck failed!", &res); } }