// Copyright 2012 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. #[crate_type = "bin"]; #[allow(non_camel_case_types)]; #[deny(warnings)]; extern mod extra; use std::os; use std::rt; use extra::getopts; use extra::getopts::groups::{optopt, optflag, reqopt}; use extra::test; use common::config; use common::mode_run_pass; use common::mode_run_fail; use common::mode_compile_fail; use common::mode_pretty; use common::mode_debug_info; use common::mode_codegen; use common::mode; use util::logv; pub mod procsrv; pub mod util; pub mod header; pub mod runtest; pub mod common; pub mod errors; pub fn main() { let args = os::args(); let config = parse_config(args); log_config(&config); run_tests(&config); } pub fn parse_config(args: ~[~str]) -> config { let groups : ~[getopts::groups::OptGroup] = ~[reqopt("", "compile-lib-path", "path to host shared libraries", "PATH"), reqopt("", "run-lib-path", "path to target shared libraries", "PATH"), reqopt("", "rustc-path", "path to rustc to use for compiling", "PATH"), optopt("", "clang-path", "path to executable for codegen tests", "PATH"), optopt("", "llvm-bin-path", "path to directory holding llvm binaries", "DIR"), reqopt("", "src-base", "directory to scan for test files", "PATH"), reqopt("", "build-base", "directory to deposit test outputs", "PATH"), reqopt("", "aux-base", "directory to find auxiliary test files", "PATH"), reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET"), reqopt("", "mode", "which sort of compile tests to run", "(compile-fail|run-fail|run-pass|pretty|debug-info)"), optflag("", "ignored", "run tests marked as ignored / xfailed"), optopt("", "runtool", "supervisor program to run tests under \ (eg. emulator, valgrind)", "PROGRAM"), optopt("", "rustcflags", "flags to pass to rustc", "FLAGS"), optflag("", "verbose", "run tests verbosely, showing all output"), optopt("", "logfile", "file to log test execution to", "FILE"), optopt("", "save-metrics", "file to save metrics to", "FILE"), optopt("", "ratchet-metrics", "file to ratchet metrics against", "FILE"), optopt("", "ratchet-noise-percent", "percent change in metrics to consider noise", "N"), optflag("", "jit", "run tests under the JIT"), optopt("", "target", "the target to build for", "TARGET"), optopt("", "adb-path", "path to the android debugger", "PATH"), optopt("", "adb-test-dir", "path to tests for the android debugger", "PATH"), optopt("", "test-shard", "run shard A, of B shards, worth of the testsuite", "A.B"), optflag("h", "help", "show this message"), ]; assert!(!args.is_empty()); let argv0 = args[0].clone(); let args_ = args.tail(); if args[1] == ~"-h" || args[1] == ~"--help" { let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); println(getopts::groups::usage(message, groups)); println(""); fail2!() } let matches = &match getopts::groups::getopts(args_, groups) { Ok(m) => m, Err(f) => fail2!("{}", f.to_err_msg()) }; if matches.opt_present("h") || matches.opt_present("help") { let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); println(getopts::groups::usage(message, groups)); println(""); fail2!() } fn opt_path(m: &getopts::Matches, nm: &str) -> Path { Path(m.opt_str(nm).unwrap()) } config { compile_lib_path: matches.opt_str("compile-lib-path").unwrap(), run_lib_path: matches.opt_str("run-lib-path").unwrap(), rustc_path: opt_path(matches, "rustc-path"), clang_path: matches.opt_str("clang-path").map_move(|s| Path(s)), llvm_bin_path: matches.opt_str("llvm-bin-path").map_move(|s| Path(s)), src_base: opt_path(matches, "src-base"), build_base: opt_path(matches, "build-base"), aux_base: opt_path(matches, "aux-base"), stage_id: matches.opt_str("stage-id").unwrap(), mode: str_mode(matches.opt_str("mode").unwrap()), run_ignored: matches.opt_present("ignored"), filter: if !matches.free.is_empty() { Some(matches.free[0].clone()) } else { None }, logfile: matches.opt_str("logfile").map_move(|s| Path(s)), save_metrics: matches.opt_str("save-metrics").map_move(|s| Path(s)), ratchet_metrics: matches.opt_str("ratchet-metrics").map_move(|s| Path(s)), ratchet_noise_percent: matches.opt_str("ratchet-noise-percent").and_then(|s| from_str::(s)), runtool: matches.opt_str("runtool"), rustcflags: matches.opt_str("rustcflags"), jit: matches.opt_present("jit"), target: opt_str2(matches.opt_str("target")).to_str(), adb_path: opt_str2(matches.opt_str("adb-path")).to_str(), adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")).to_str(), adb_device_status: if (opt_str2(matches.opt_str("target")) == ~"arm-linux-androideabi") { if (opt_str2(matches.opt_str("adb-test-dir")) != ~"(none)" && opt_str2(matches.opt_str("adb-test-dir")) != ~"") { true } else { false } } else { false }, test_shard: test::opt_shard(matches.opt_str("test-shard")), verbose: matches.opt_present("verbose") } } pub fn log_config(config: &config) { let c = config; logv(c, format!("configuration:")); logv(c, format!("compile_lib_path: {}", config.compile_lib_path)); logv(c, format!("run_lib_path: {}", config.run_lib_path)); logv(c, format!("rustc_path: {}", config.rustc_path.to_str())); logv(c, format!("src_base: {}", config.src_base.to_str())); logv(c, format!("build_base: {}", config.build_base.to_str())); logv(c, format!("stage_id: {}", config.stage_id)); logv(c, format!("mode: {}", mode_str(config.mode))); logv(c, format!("run_ignored: {}", config.run_ignored)); logv(c, format!("filter: {}", opt_str(&config.filter))); logv(c, format!("runtool: {}", opt_str(&config.runtool))); logv(c, format!("rustcflags: {}", opt_str(&config.rustcflags))); logv(c, format!("jit: {}", config.jit)); logv(c, format!("target: {}", config.target)); logv(c, format!("adb_path: {}", config.adb_path)); logv(c, format!("adb_test_dir: {}", config.adb_test_dir)); logv(c, format!("adb_device_status: {}", config.adb_device_status)); match config.test_shard { None => logv(c, ~"test_shard: (all)"), Some((a,b)) => logv(c, format!("test_shard: {}.{}", a, b)) } logv(c, format!("verbose: {}", config.verbose)); logv(c, format!("\n")); } pub fn opt_str<'a>(maybestr: &'a Option<~str>) -> &'a str { match *maybestr { None => "(none)", Some(ref s) => { let s: &'a str = *s; s } } } pub fn opt_str2(maybestr: Option<~str>) -> ~str { match maybestr { None => ~"(none)", Some(s) => { s } } } pub fn str_opt(maybestr: ~str) -> Option<~str> { if maybestr != ~"(none)" { Some(maybestr) } else { None } } pub fn str_mode(s: ~str) -> mode { match s { ~"compile-fail" => mode_compile_fail, ~"run-fail" => mode_run_fail, ~"run-pass" => mode_run_pass, ~"pretty" => mode_pretty, ~"debug-info" => mode_debug_info, ~"codegen" => mode_codegen, _ => fail2!("invalid mode") } } pub fn mode_str(mode: mode) -> ~str { match mode { mode_compile_fail => ~"compile-fail", mode_run_fail => ~"run-fail", mode_run_pass => ~"run-pass", mode_pretty => ~"pretty", mode_debug_info => ~"debug-info", mode_codegen => ~"codegen", } } pub fn run_tests(config: &config) { let opts = test_opts(config); let tests = make_tests(config); // sadly osx needs some file descriptor limits raised for running tests in // parallel (especially when we have lots and lots of child processes). // For context, see #8904 rt::test::prepare_for_lots_of_tests(); let res = test::run_tests_console(&opts, tests); if !res { fail2!("Some tests failed"); } } pub fn test_opts(config: &config) -> test::TestOpts { test::TestOpts { filter: config.filter.clone(), run_ignored: config.run_ignored, logfile: config.logfile.clone(), run_tests: true, run_benchmarks: true, ratchet_metrics: config.ratchet_metrics.clone(), ratchet_noise_percent: config.ratchet_noise_percent.clone(), save_metrics: config.save_metrics.clone(), test_shard: config.test_shard.clone() } } pub fn make_tests(config: &config) -> ~[test::TestDescAndFn] { debug2!("making tests from {}", config.src_base.to_str()); let mut tests = ~[]; let dirs = os::list_dir_path(&config.src_base); for file in dirs.iter() { let file = file.clone(); debug2!("inspecting file {}", file.to_str()); if is_test(config, &file) { let t = do make_test(config, &file) { match config.mode { mode_codegen => make_metrics_test_closure(config, &file), _ => make_test_closure(config, &file) } }; tests.push(t) } } tests } pub fn is_test(config: &config, testfile: &Path) -> bool { // Pretty-printer does not work with .rc files yet let valid_extensions = match config.mode { mode_pretty => ~[~".rs"], _ => ~[~".rc", ~".rs"] }; let invalid_prefixes = ~[~".", ~"#", ~"~"]; let name = testfile.filename().unwrap(); let mut valid = false; for ext in valid_extensions.iter() { if name.ends_with(*ext) { valid = true; } } for pre in invalid_prefixes.iter() { if name.starts_with(*pre) { valid = false; } } return valid; } pub fn make_test(config: &config, testfile: &Path, f: &fn()->test::TestFn) -> test::TestDescAndFn { test::TestDescAndFn { desc: test::TestDesc { name: make_test_name(config, testfile), ignore: header::is_test_ignored(config, testfile), should_fail: false }, testfn: f(), } } pub fn make_test_name(config: &config, testfile: &Path) -> test::TestName { // Try to elide redundant long paths fn shorten(path: &Path) -> ~str { let filename = path.filename(); let p = path.pop(); let dir = p.filename(); format!("{}/{}", dir.unwrap_or(""), filename.unwrap_or("")) } test::DynTestName(format!("[{}] {}", mode_str(config.mode), shorten(testfile))) } pub fn make_test_closure(config: &config, testfile: &Path) -> test::TestFn { use std::cell::Cell; let config = Cell::new((*config).clone()); let testfile = Cell::new(testfile.to_str()); test::DynTestFn(|| { runtest::run(config.take(), testfile.take()) }) } pub fn make_metrics_test_closure(config: &config, testfile: &Path) -> test::TestFn { use std::cell::Cell; let config = Cell::new((*config).clone()); let testfile = Cell::new(testfile.to_str()); test::DynMetricFn(|mm| { runtest::run_metrics(config.take(), testfile.take(), mm) }) }