From bd4b6712181b49c5110208d1a5c4378e2788fd95 Mon Sep 17 00:00:00 2001 From: John Renner Date: Mon, 26 Aug 2019 17:28:43 -0700 Subject: [PATCH] Move compiletest out of tree --- .gitmodules | 3 + src/bootstrap/tool.rs | 2 +- src/tools/compiletest | 1 + src/tools/compiletest/Cargo.toml | 24 - src/tools/compiletest/src/common.rs | 374 -- src/tools/compiletest/src/errors.rs | 184 - src/tools/compiletest/src/header.rs | 973 ----- src/tools/compiletest/src/header/tests.rs | 27 - src/tools/compiletest/src/json.rs | 269 -- src/tools/compiletest/src/main.rs | 1098 ------ src/tools/compiletest/src/raise_fd_limit.rs | 63 - src/tools/compiletest/src/read2.rs | 205 - src/tools/compiletest/src/runtest.rs | 3711 ------------------- src/tools/compiletest/src/runtest/tests.rs | 61 - src/tools/compiletest/src/tests.rs | 51 - src/tools/compiletest/src/util.rs | 162 - src/tools/compiletest/src/util/tests.rs | 32 - src/tools/tidy/src/extdeps.rs | 1 + 18 files changed, 6 insertions(+), 7235 deletions(-) create mode 160000 src/tools/compiletest delete mode 100644 src/tools/compiletest/Cargo.toml delete mode 100644 src/tools/compiletest/src/common.rs delete mode 100644 src/tools/compiletest/src/errors.rs delete mode 100644 src/tools/compiletest/src/header.rs delete mode 100644 src/tools/compiletest/src/header/tests.rs delete mode 100644 src/tools/compiletest/src/json.rs delete mode 100644 src/tools/compiletest/src/main.rs delete mode 100644 src/tools/compiletest/src/raise_fd_limit.rs delete mode 100644 src/tools/compiletest/src/read2.rs delete mode 100644 src/tools/compiletest/src/runtest.rs delete mode 100644 src/tools/compiletest/src/runtest/tests.rs delete mode 100644 src/tools/compiletest/src/tests.rs delete mode 100644 src/tools/compiletest/src/util.rs delete mode 100644 src/tools/compiletest/src/util/tests.rs diff --git a/.gitmodules b/.gitmodules index f64e21c5af0e4..7601c53f926e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -47,3 +47,6 @@ [submodule "src/doc/embedded-book"] path = src/doc/embedded-book url = https://github.com/rust-embedded/book.git +[submodule "src/tools/compiletest"] + path = src/tools/compiletest + url = https://github.com/djrenren/compiletest.git diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index 54fe26f18e741..3c4fd2f83ea0d 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -384,7 +384,7 @@ bootstrap_tool!( Tidy, "src/tools/tidy", "tidy"; Linkchecker, "src/tools/linkchecker", "linkchecker"; CargoTest, "src/tools/cargotest", "cargotest"; - Compiletest, "src/tools/compiletest", "compiletest", llvm_tools = true; + Compiletest, "src/tools/compiletest", "compiletest", llvm_tools = true, is_external_tool = true; BuildManifest, "src/tools/build-manifest", "build-manifest"; RemoteTestClient, "src/tools/remote-test-client", "remote-test-client"; RustInstaller, "src/tools/rust-installer", "fabricate", is_external_tool = true; diff --git a/src/tools/compiletest b/src/tools/compiletest new file mode 160000 index 0000000000000..b08141d2e5a58 --- /dev/null +++ b/src/tools/compiletest @@ -0,0 +1 @@ +Subproject commit b08141d2e5a58a6058eef50e3da544ff6dcf4804 diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml deleted file mode 100644 index e759ad1f35dde..0000000000000 --- a/src/tools/compiletest/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -authors = ["The Rust Project Developers"] -name = "compiletest" -version = "0.0.0" -edition = "2018" - -[dependencies] -diff = "0.1.10" -env_logger = { version = "0.5", default-features = false } -getopts = "0.2" -log = "0.4" -regex = "1.0" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -rustfix = "0.4.1" -lazy_static = "1.0" -walkdir = "2" - -[target.'cfg(unix)'.dependencies] -libc = "0.2" - -[target.'cfg(windows)'.dependencies] -miow = "0.3" -winapi = { version = "0.3", features = ["winerror"] } diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs deleted file mode 100644 index 66e030e979355..0000000000000 --- a/src/tools/compiletest/src/common.rs +++ /dev/null @@ -1,374 +0,0 @@ -pub use self::Mode::*; - -use std::ffi::OsString; -use std::fmt; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -use test::ColorConfig; -use crate::util::PathBufExt; - -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Mode { - CompileFail, - RunFail, - RunPassValgrind, - Pretty, - DebugInfoCdb, - DebugInfoGdbLldb, - DebugInfoGdb, - DebugInfoLldb, - Codegen, - Rustdoc, - CodegenUnits, - Incremental, - RunMake, - Ui, - JsDocTest, - MirOpt, - Assembly, -} - -impl Mode { - pub fn disambiguator(self) -> &'static str { - // Pretty-printing tests could run concurrently, and if they do, - // they need to keep their output segregated. Same is true for debuginfo tests that - // can be run on cdb, gdb, and lldb. - match self { - Pretty => ".pretty", - DebugInfoCdb => ".cdb", - DebugInfoGdb => ".gdb", - DebugInfoLldb => ".lldb", - _ => "", - } - } -} - -impl FromStr for Mode { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "compile-fail" => Ok(CompileFail), - "run-fail" => Ok(RunFail), - "run-pass-valgrind" => Ok(RunPassValgrind), - "pretty" => Ok(Pretty), - "debuginfo-cdb" => Ok(DebugInfoCdb), - "debuginfo-gdb+lldb" => Ok(DebugInfoGdbLldb), - "debuginfo-lldb" => Ok(DebugInfoLldb), - "debuginfo-gdb" => Ok(DebugInfoGdb), - "codegen" => Ok(Codegen), - "rustdoc" => Ok(Rustdoc), - "codegen-units" => Ok(CodegenUnits), - "incremental" => Ok(Incremental), - "run-make" => Ok(RunMake), - "ui" => Ok(Ui), - "js-doc-test" => Ok(JsDocTest), - "mir-opt" => Ok(MirOpt), - "assembly" => Ok(Assembly), - _ => Err(()), - } - } -} - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - CompileFail => "compile-fail", - RunFail => "run-fail", - RunPassValgrind => "run-pass-valgrind", - Pretty => "pretty", - DebugInfoCdb => "debuginfo-cdb", - DebugInfoGdbLldb => "debuginfo-gdb+lldb", - DebugInfoGdb => "debuginfo-gdb", - DebugInfoLldb => "debuginfo-lldb", - Codegen => "codegen", - Rustdoc => "rustdoc", - CodegenUnits => "codegen-units", - Incremental => "incremental", - RunMake => "run-make", - Ui => "ui", - JsDocTest => "js-doc-test", - MirOpt => "mir-opt", - Assembly => "assembly", - }; - fmt::Display::fmt(s, f) - } -} - -#[derive(Clone, Copy, PartialEq, Debug, Hash)] -pub enum PassMode { - Check, - Build, - Run, -} - -impl FromStr for PassMode { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "check" => Ok(PassMode::Check), - "build" => Ok(PassMode::Build), - "run" => Ok(PassMode::Run), - _ => Err(()), - } - } -} - -impl fmt::Display for PassMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match *self { - PassMode::Check => "check", - PassMode::Build => "build", - PassMode::Run => "run", - }; - fmt::Display::fmt(s, f) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum CompareMode { - Nll, - Polonius, -} - -impl CompareMode { - pub(crate) fn to_str(&self) -> &'static str { - match *self { - CompareMode::Nll => "nll", - CompareMode::Polonius => "polonius", - } - } - - pub fn parse(s: String) -> CompareMode { - match s.as_str() { - "nll" => CompareMode::Nll, - "polonius" => CompareMode::Polonius, - x => panic!("unknown --compare-mode option: {}", x), - } - } -} - -/// Configuration for compiletest -#[derive(Clone)] -pub struct Config { - /// `true` to to overwrite stderr/stdout files instead of complaining about changes in output. - pub bless: bool, - - /// The library paths required for running the compiler. - pub compile_lib_path: PathBuf, - - /// The library paths required for running compiled programs. - pub run_lib_path: PathBuf, - - /// The rustc executable. - pub rustc_path: PathBuf, - - /// The rustdoc executable. - pub rustdoc_path: Option, - - /// The Python executable to use for LLDB. - pub lldb_python: String, - - /// The Python executable to use for htmldocck. - pub docck_python: String, - - /// The LLVM `FileCheck` binary path. - pub llvm_filecheck: Option, - - /// Path to LLVM's bin directory. - pub llvm_bin_dir: Option, - - /// The valgrind path. - pub valgrind_path: Option, - - /// Whether to fail if we can't run run-pass-valgrind tests under valgrind - /// (or, alternatively, to silently run them like regular run-pass tests). - pub force_valgrind: bool, - - /// The path to the Clang executable to run Clang-based tests with. If - /// `None` then these tests will be ignored. - pub run_clang_based_tests_with: Option, - - /// The directory containing the tests to run - pub src_base: PathBuf, - - /// The directory where programs should be built - pub build_base: PathBuf, - - /// The name of the stage being built (stage1, etc) - pub stage_id: String, - - /// The test mode, compile-fail, run-fail, ui - pub mode: Mode, - - /// Run ignored tests - pub run_ignored: bool, - - /// Only run tests that match this filter - pub filter: Option, - - /// Exactly match the filter, rather than a substring - pub filter_exact: bool, - - /// Force the pass mode of a check/build/run-pass test to this mode. - pub force_pass_mode: Option, - - /// Write out a parseable log of tests that were run - pub logfile: Option, - - /// A command line to prefix program execution with, - /// for running under valgrind - pub runtool: Option, - - /// Flags to pass to the compiler when building for the host - pub host_rustcflags: Option, - - /// Flags to pass to the compiler when building for the target - pub target_rustcflags: Option, - - /// Target system to be tested - pub target: String, - - /// Host triple for the compiler being invoked - pub host: String, - - /// Path to / name of the Microsoft Console Debugger (CDB) executable - pub cdb: Option, - - /// Path to / name of the GDB executable - pub gdb: Option, - - /// Version of GDB, encoded as ((major * 1000) + minor) * 1000 + patch - pub gdb_version: Option, - - /// Whether GDB has native rust support - pub gdb_native_rust: bool, - - /// Version of LLDB - pub lldb_version: Option, - - /// Whether LLDB has native rust support - pub lldb_native_rust: bool, - - /// Version of LLVM - pub llvm_version: Option, - - /// Is LLVM a system LLVM - pub system_llvm: bool, - - /// Path to the android tools - pub android_cross_path: PathBuf, - - /// Extra parameter to run adb on arm-linux-androideabi - pub adb_path: String, - - /// Extra parameter to run test suite on arm-linux-androideabi - pub adb_test_dir: String, - - /// status whether android device available or not - pub adb_device_status: bool, - - /// the path containing LLDB's Python module - pub lldb_python_dir: Option, - - /// Explain what's going on - pub verbose: bool, - - /// Print one character per test instead of one line - pub quiet: bool, - - /// Whether to use colors in test. - pub color: ColorConfig, - - /// where to find the remote test client process, if we're using it - pub remote_test_client: Option, - - /// mode describing what file the actual ui output will be compared to - pub compare_mode: Option, - - /// If true, this will generate a coverage file with UI test files that run `MachineApplicable` - /// diagnostics but are missing `run-rustfix` annotations. The generated coverage file is - /// created in `//rustfix_missing_coverage.txt` - pub rustfix_coverage: bool, - - // Configuration for various run-make tests frobbing things like C compilers - // or querying about various LLVM component information. - pub cc: String, - pub cxx: String, - pub cflags: String, - pub ar: String, - pub linker: Option, - pub llvm_components: String, - pub llvm_cxxflags: String, - - /// Path to a NodeJS executable. Used for JS doctests, emscripten and WASM tests - pub nodejs: Option, -} - -#[derive(Debug, Clone)] -pub struct TestPaths { - pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs - pub relative_dir: PathBuf, // e.g., foo/bar -} - -/// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`. -pub fn expected_output_path( - testpaths: &TestPaths, - revision: Option<&str>, - compare_mode: &Option, - kind: &str, -) -> PathBuf { - assert!(UI_EXTENSIONS.contains(&kind)); - let mut parts = Vec::new(); - - if let Some(x) = revision { - parts.push(x); - } - if let Some(ref x) = *compare_mode { - parts.push(x.to_str()); - } - parts.push(kind); - - let extension = parts.join("."); - testpaths.file.with_extension(extension) -} - -pub const UI_EXTENSIONS: &[&str] = &[UI_STDERR, UI_STDOUT, UI_FIXED]; -pub const UI_STDERR: &str = "stderr"; -pub const UI_STDOUT: &str = "stdout"; -pub const UI_FIXED: &str = "fixed"; - -/// Absolute path to the directory where all output for all tests in the given -/// `relative_dir` group should reside. Example: -/// /path/to/build/host-triple/test/ui/relative/ -/// This is created early when tests are collected to avoid race conditions. -pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf { - config.build_base.join(relative_dir) -} - -/// Generates a unique name for the test, such as `testname.revision.mode`. -pub fn output_testname_unique( - config: &Config, - testpaths: &TestPaths, - revision: Option<&str>, -) -> PathBuf { - let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str()); - PathBuf::from(&testpaths.file.file_stem().unwrap()) - .with_extra_extension(revision.unwrap_or("")) - .with_extra_extension(mode) -} - -/// Absolute path to the directory where all output for the given -/// test/revision should reside. Example: -/// /path/to/build/host-triple/test/ui/relative/testname.revision.mode/ -pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { - output_relative_path(config, &testpaths.relative_dir) - .join(output_testname_unique(config, testpaths, revision)) -} - -/// Absolute path to the base filename used as output for the given -/// test/revision. Example: -/// /path/to/build/host-triple/test/ui/relative/testname.revision.mode/testname -pub fn output_base_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { - output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap()) -} diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs deleted file mode 100644 index 5b3936ffc1e3b..0000000000000 --- a/src/tools/compiletest/src/errors.rs +++ /dev/null @@ -1,184 +0,0 @@ -use self::WhichLine::*; - -use std::fmt; -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::Path; -use std::str::FromStr; - -use log::*; - -#[derive(Clone, Debug, PartialEq)] -pub enum ErrorKind { - Help, - Error, - Note, - Suggestion, - Warning, -} - -impl FromStr for ErrorKind { - type Err = (); - fn from_str(s: &str) -> Result { - let s = s.to_uppercase(); - let part0: &str = s.split(':').next().unwrap(); - match part0 { - "HELP" => Ok(ErrorKind::Help), - "ERROR" => Ok(ErrorKind::Error), - "NOTE" => Ok(ErrorKind::Note), - "SUGGESTION" => Ok(ErrorKind::Suggestion), - "WARN" | "WARNING" => Ok(ErrorKind::Warning), - _ => Err(()), - } - } -} - -impl fmt::Display for ErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - ErrorKind::Help => write!(f, "help message"), - ErrorKind::Error => write!(f, "error"), - ErrorKind::Note => write!(f, "note"), - ErrorKind::Suggestion => write!(f, "suggestion"), - ErrorKind::Warning => write!(f, "warning"), - } - } -} - -#[derive(Debug)] -pub struct Error { - pub line_num: usize, - /// What kind of message we expect (e.g., warning, error, suggestion). - /// `None` if not specified or unknown message kind. - pub kind: Option, - pub msg: String, -} - -#[derive(PartialEq, Debug)] -enum WhichLine { - ThisLine, - FollowPrevious(usize), - AdjustBackward(usize), -} - -/// Looks for either "//~| KIND MESSAGE" or "//~^^... KIND MESSAGE" -/// The former is a "follow" that inherits its target from the preceding line; -/// the latter is an "adjusts" that goes that many lines up. -/// -/// Goal is to enable tests both like: //~^^^ ERROR go up three -/// and also //~^ ERROR message one for the preceding line, and -/// //~| ERROR message two for that same line. -/// -/// If cfg is not None (i.e., in an incremental test), then we look -/// for `//[X]~` instead, where `X` is the current `cfg`. -pub fn load_errors(testfile: &Path, cfg: Option<&str>) -> Vec { - let rdr = BufReader::new(File::open(testfile).unwrap()); - - // `last_nonfollow_error` tracks the most recently seen - // line with an error template that did not use the - // follow-syntax, "//~| ...". - // - // (pnkfelix could not find an easy way to compose Iterator::scan - // and Iterator::filter_map to pass along this information into - // `parse_expected`. So instead I am storing that state here and - // updating it in the map callback below.) - let mut last_nonfollow_error = None; - - let tag = match cfg { - Some(rev) => format!("//[{}]~", rev), - None => "//~".to_string(), - }; - - rdr.lines() - .enumerate() - .filter_map(|(line_num, line)| { - parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), &tag).map( - |(which, error)| { - match which { - FollowPrevious(_) => {} - _ => last_nonfollow_error = Some(error.line_num), - } - error - }, - ) - }) - .collect() -} - -fn parse_expected( - last_nonfollow_error: Option, - line_num: usize, - line: &str, - tag: &str, -) -> Option<(WhichLine, Error)> { - let start = line.find(tag)?; - let (follow, adjusts) = if line[start + tag.len()..].chars().next().unwrap() == '|' { - (true, 0) - } else { - ( - false, - line[start + tag.len()..] - .chars() - .take_while(|c| *c == '^') - .count(), - ) - }; - let kind_start = start + tag.len() + adjusts + (follow as usize); - let (kind, msg); - match line[kind_start..] - .split_whitespace() - .next() - .expect("Encountered unexpected empty comment") - .parse::() - { - Ok(k) => { - // If we find `//~ ERROR foo` or something like that: - kind = Some(k); - let letters = line[kind_start..].chars(); - msg = letters - .skip_while(|c| c.is_whitespace()) - .skip_while(|c| !c.is_whitespace()) - .collect::(); - } - Err(_) => { - // Otherwise we found `//~ foo`: - kind = None; - let letters = line[kind_start..].chars(); - msg = letters - .skip_while(|c| c.is_whitespace()) - .collect::(); - } - } - let msg = msg.trim().to_owned(); - - let (which, line_num) = if follow { - assert_eq!(adjusts, 0, "use either //~| or //~^, not both."); - let line_num = last_nonfollow_error.expect( - "encountered //~| without \ - preceding //~^ line.", - ); - (FollowPrevious(line_num), line_num) - } else { - let which = if adjusts > 0 { - AdjustBackward(adjusts) - } else { - ThisLine - }; - let line_num = line_num - adjusts; - (which, line_num) - }; - - debug!( - "line={} tag={:?} which={:?} kind={:?} msg={:?}", - line_num, tag, which, kind, msg - ); - Some(( - which, - Error { - line_num, - kind, - msg, - }, - )) -} diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs deleted file mode 100644 index 076ad87c70fc4..0000000000000 --- a/src/tools/compiletest/src/header.rs +++ /dev/null @@ -1,973 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::{Path, PathBuf}; - -use log::*; - -use crate::common::{self, CompareMode, Config, Mode, PassMode}; -use crate::util; -use crate::extract_gdb_version; - -#[cfg(test)] -mod tests; - -/// Whether to ignore the test. -#[derive(Clone, Copy, PartialEq, Debug)] -pub enum Ignore { - /// Runs it. - Run, - /// Ignore it totally. - Ignore, - /// Ignore only the gdb test, but run the lldb test. - IgnoreGdb, - /// Ignore only the lldb test, but run the gdb test. - IgnoreLldb, -} - -impl Ignore { - pub fn can_run_gdb(&self) -> bool { - *self == Ignore::Run || *self == Ignore::IgnoreLldb - } - - pub fn can_run_lldb(&self) -> bool { - *self == Ignore::Run || *self == Ignore::IgnoreGdb - } - - pub fn no_gdb(&self) -> Ignore { - match *self { - Ignore::Run => Ignore::IgnoreGdb, - Ignore::IgnoreGdb => Ignore::IgnoreGdb, - _ => Ignore::Ignore, - } - } - - pub fn no_lldb(&self) -> Ignore { - match *self { - Ignore::Run => Ignore::IgnoreLldb, - Ignore::IgnoreLldb => Ignore::IgnoreLldb, - _ => Ignore::Ignore, - } - } -} - -/// The result of parse_cfg_name_directive. -#[derive(Clone, Copy, PartialEq, Debug)] -enum ParsedNameDirective { - /// No match. - NoMatch, - /// Match. - Match, - /// Mode was DebugInfoGdbLldb and this matched gdb. - MatchGdb, - /// Mode was DebugInfoGdbLldb and this matched lldb. - MatchLldb, -} - -/// Properties which must be known very early, before actually running -/// the test. -pub struct EarlyProps { - pub ignore: Ignore, - pub should_fail: bool, - pub aux: Vec, - pub revisions: Vec, -} - -impl EarlyProps { - pub fn from_file(config: &Config, testfile: &Path) -> Self { - let mut props = EarlyProps { - ignore: Ignore::Run, - should_fail: false, - aux: Vec::new(), - revisions: vec![], - }; - - if config.mode == common::DebugInfoGdbLldb { - if config.lldb_python_dir.is_none() { - props.ignore = props.ignore.no_lldb(); - } - if config.gdb_version.is_none() { - props.ignore = props.ignore.no_gdb(); - } - } else if config.mode == common::DebugInfoCdb { - if config.cdb.is_none() { - props.ignore = Ignore::Ignore; - } - } - - let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some(); - let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(); - - iter_header(testfile, None, &mut |ln| { - // we should check if any only- exists and if it exists - // and does not matches the current platform, skip the test - if props.ignore != Ignore::Ignore { - props.ignore = match config.parse_cfg_name_directive(ln, "ignore") { - ParsedNameDirective::Match => Ignore::Ignore, - ParsedNameDirective::NoMatch => props.ignore, - ParsedNameDirective::MatchGdb => props.ignore.no_gdb(), - ParsedNameDirective::MatchLldb => props.ignore.no_lldb(), - }; - - if config.has_cfg_prefix(ln, "only") { - props.ignore = match config.parse_cfg_name_directive(ln, "only") { - ParsedNameDirective::Match => props.ignore, - ParsedNameDirective::NoMatch => Ignore::Ignore, - ParsedNameDirective::MatchLldb => props.ignore.no_gdb(), - ParsedNameDirective::MatchGdb => props.ignore.no_lldb(), - }; - } - - if ignore_llvm(config, ln) { - props.ignore = Ignore::Ignore; - } - - if config.run_clang_based_tests_with.is_none() && - config.parse_needs_matching_clang(ln) { - props.ignore = Ignore::Ignore; - } - - if !rustc_has_profiler_support && - config.parse_needs_profiler_support(ln) { - props.ignore = Ignore::Ignore; - } - - if !rustc_has_sanitizer_support && - config.parse_needs_sanitizer_support(ln) { - props.ignore = Ignore::Ignore; - } - } - - if (config.mode == common::DebugInfoGdb || config.mode == common::DebugInfoGdbLldb) && - props.ignore.can_run_gdb() && ignore_gdb(config, ln) { - props.ignore = props.ignore.no_gdb(); - } - - if (config.mode == common::DebugInfoLldb || config.mode == common::DebugInfoGdbLldb) && - props.ignore.can_run_lldb() && ignore_lldb(config, ln) { - props.ignore = props.ignore.no_lldb(); - } - - if let Some(s) = config.parse_aux_build(ln) { - props.aux.push(s); - } - - if let Some(r) = config.parse_revisions(ln) { - props.revisions.extend(r); - } - - props.should_fail = props.should_fail || config.parse_name_directive(ln, "should-fail"); - }); - - return props; - - fn ignore_gdb(config: &Config, line: &str) -> bool { - if let Some(actual_version) = config.gdb_version { - if line.starts_with("min-gdb-version") { - let (start_ver, end_ver) = extract_gdb_version_range(line); - - if start_ver != end_ver { - panic!("Expected single GDB version") - } - // Ignore if actual version is smaller the minimum required - // version - actual_version < start_ver - } else if line.starts_with("ignore-gdb-version") { - let (min_version, max_version) = extract_gdb_version_range(line); - - if max_version < min_version { - panic!("Malformed GDB version range: max < min") - } - - actual_version >= min_version && actual_version <= max_version - } else { - false - } - } else { - false - } - } - - // Takes a directive of the form "ignore-gdb-version [- ]", - // returns the numeric representation of and as - // tuple: ( as u32, as u32) - // If the part is omitted, the second component of the tuple - // is the same as . - fn extract_gdb_version_range(line: &str) -> (u32, u32) { - const ERROR_MESSAGE: &'static str = "Malformed GDB version directive"; - - let range_components = line.split(&[' ', '-'][..]) - .filter(|word| !word.is_empty()) - .map(extract_gdb_version) - .skip_while(Option::is_none) - .take(3) // 3 or more = invalid, so take at most 3. - .collect::>>(); - - match range_components.len() { - 1 => { - let v = range_components[0].unwrap(); - (v, v) - } - 2 => { - let v_min = range_components[0].unwrap(); - let v_max = range_components[1].expect(ERROR_MESSAGE); - (v_min, v_max) - } - _ => panic!(ERROR_MESSAGE), - } - } - - fn ignore_lldb(config: &Config, line: &str) -> bool { - if let Some(ref actual_version) = config.lldb_version { - if line.starts_with("min-lldb-version") { - let min_version = line.trim_end() - .rsplit(' ') - .next() - .expect("Malformed lldb version directive"); - // Ignore if actual version is smaller the minimum required - // version - lldb_version_to_int(actual_version) < lldb_version_to_int(min_version) - } else if line.starts_with("rust-lldb") && !config.lldb_native_rust { - true - } else { - false - } - } else { - false - } - } - - fn ignore_llvm(config: &Config, line: &str) -> bool { - if config.system_llvm && line.starts_with("no-system-llvm") { - return true; - } - if let Some(ref actual_version) = config.llvm_version { - if line.starts_with("min-llvm-version") { - let min_version = line.trim_end() - .rsplit(' ') - .next() - .expect("Malformed llvm version directive"); - // Ignore if actual version is smaller the minimum required - // version - &actual_version[..] < min_version - } else if line.starts_with("min-system-llvm-version") { - let min_version = line.trim_end() - .rsplit(' ') - .next() - .expect("Malformed llvm version directive"); - // Ignore if using system LLVM and actual version - // is smaller the minimum required version - config.system_llvm && &actual_version[..] < min_version - } else if line.starts_with("ignore-llvm-version") { - // Syntax is: "ignore-llvm-version [- ]" - let range_components = line.split(' ') - .skip(1) // Skip the directive. - .map(|s| s.trim()) - .filter(|word| !word.is_empty() && word != &"-") - .take(3) // 3 or more = invalid, so take at most 3. - .collect::>(); - match range_components.len() { - 1 => { - &actual_version[..] == range_components[0] - } - 2 => { - let v_min = range_components[0]; - let v_max = range_components[1]; - if v_max < v_min { - panic!("Malformed LLVM version range: max < min") - } - // Ignore if version lies inside of range. - &actual_version[..] >= v_min && &actual_version[..] <= v_max - } - _ => panic!("Malformed LLVM version directive"), - } - } else { - false - } - } else { - false - } - } - } -} - -#[derive(Clone, Debug)] -pub struct TestProps { - // Lines that should be expected, in order, on standard out - pub error_patterns: Vec, - // Extra flags to pass to the compiler - pub compile_flags: Vec, - // Extra flags to pass when the compiled code is run (such as --bench) - pub run_flags: Option, - // If present, the name of a file that this test should match when - // pretty-printed - pub pp_exact: Option, - // Other crates that should be compiled (typically from the same - // directory as the test, but for backwards compatibility reasons - // we also check the auxiliary directory) - pub aux_builds: Vec, - // A list of crates to pass '--extern-private name:PATH' flags for - // This should be a subset of 'aux_build' - // FIXME: Replace this with a better solution: https://github.com/rust-lang/rust/pull/54020 - pub extern_private: Vec, - // Environment settings to use for compiling - pub rustc_env: Vec<(String, String)>, - // Environment variables to unset prior to compiling. - // Variables are unset before applying 'rustc_env'. - pub unset_rustc_env: Vec, - // Environment settings to use during execution - pub exec_env: Vec<(String, String)>, - // Lines to check if they appear in the expected debugger output - pub check_lines: Vec, - // Build documentation for all specified aux-builds as well - pub build_aux_docs: bool, - // Flag to force a crate to be built with the host architecture - pub force_host: bool, - // Check stdout for error-pattern output as well as stderr - pub check_stdout: bool, - // For UI tests, allows compiler to generate arbitrary output to stdout - pub dont_check_compiler_stdout: bool, - // For UI tests, allows compiler to generate arbitrary output to stderr - pub dont_check_compiler_stderr: bool, - // Don't force a --crate-type=dylib flag on the command line - // - // Set this for example if you have an auxiliary test file that contains - // a proc-macro and needs `#![crate_type = "proc-macro"]`. This ensures - // that the aux file is compiled as a `proc-macro` and not as a `dylib`. - pub no_prefer_dynamic: bool, - // Run --pretty expanded when running pretty printing tests - pub pretty_expanded: bool, - // Which pretty mode are we testing with, default to 'normal' - pub pretty_mode: String, - // Only compare pretty output and don't try compiling - pub pretty_compare_only: bool, - // Patterns which must not appear in the output of a cfail test. - pub forbid_output: Vec, - // Revisions to test for incremental compilation. - pub revisions: Vec, - // Directory (if any) to use for incremental compilation. This is - // not set by end-users; rather it is set by the incremental - // testing harness and used when generating compilation - // arguments. (In particular, it propagates to the aux-builds.) - pub incremental_dir: Option, - // How far should the test proceed while still passing. - pass_mode: Option, - // Ignore `--pass` overrides from the command line for this test. - ignore_pass: bool, - // rustdoc will test the output of the `--test` option - pub check_test_line_numbers_match: bool, - // Do not pass `-Z ui-testing` to UI tests - pub disable_ui_testing_normalization: bool, - // customized normalization rules - pub normalize_stdout: Vec<(String, String)>, - pub normalize_stderr: Vec<(String, String)>, - pub failure_status: i32, - // Whether or not `rustfix` should apply the `CodeSuggestion`s of this test and compile the - // resulting Rust code. - pub run_rustfix: bool, - // If true, `rustfix` will only apply `MachineApplicable` suggestions. - pub rustfix_only_machine_applicable: bool, - pub assembly_output: Option, -} - -impl TestProps { - pub fn new() -> Self { - TestProps { - error_patterns: vec![], - compile_flags: vec![], - run_flags: None, - pp_exact: None, - aux_builds: vec![], - extern_private: vec![], - revisions: vec![], - rustc_env: vec![], - unset_rustc_env: vec![], - exec_env: vec![], - check_lines: vec![], - build_aux_docs: false, - force_host: false, - check_stdout: false, - dont_check_compiler_stdout: false, - dont_check_compiler_stderr: false, - no_prefer_dynamic: false, - pretty_expanded: false, - pretty_mode: "normal".to_string(), - pretty_compare_only: false, - forbid_output: vec![], - incremental_dir: None, - pass_mode: None, - ignore_pass: false, - check_test_line_numbers_match: false, - disable_ui_testing_normalization: false, - normalize_stdout: vec![], - normalize_stderr: vec![], - failure_status: -1, - run_rustfix: false, - rustfix_only_machine_applicable: false, - assembly_output: None, - } - } - - pub fn from_aux_file(&self, testfile: &Path, cfg: Option<&str>, config: &Config) -> Self { - let mut props = TestProps::new(); - - // copy over select properties to the aux build: - props.incremental_dir = self.incremental_dir.clone(); - props.load_from(testfile, cfg, config); - - props - } - - pub fn from_file(testfile: &Path, cfg: Option<&str>, config: &Config) -> Self { - let mut props = TestProps::new(); - props.load_from(testfile, cfg, config); - props - } - - /// Loads properties from `testfile` into `props`. If a property is - /// tied to a particular revision `foo` (indicated by writing - /// `//[foo]`), then the property is ignored unless `cfg` is - /// `Some("foo")`. - fn load_from(&mut self, testfile: &Path, cfg: Option<&str>, config: &Config) { - iter_header(testfile, cfg, &mut |ln| { - if let Some(ep) = config.parse_error_pattern(ln) { - self.error_patterns.push(ep); - } - - if let Some(flags) = config.parse_compile_flags(ln) { - self.compile_flags - .extend(flags.split_whitespace().map(|s| s.to_owned())); - } - - if let Some(edition) = config.parse_edition(ln) { - self.compile_flags.push(format!("--edition={}", edition)); - } - - if let Some(r) = config.parse_revisions(ln) { - self.revisions.extend(r); - } - - if self.run_flags.is_none() { - self.run_flags = config.parse_run_flags(ln); - } - - if self.pp_exact.is_none() { - self.pp_exact = config.parse_pp_exact(ln, testfile); - } - - if !self.build_aux_docs { - self.build_aux_docs = config.parse_build_aux_docs(ln); - } - - if !self.force_host { - self.force_host = config.parse_force_host(ln); - } - - if !self.check_stdout { - self.check_stdout = config.parse_check_stdout(ln); - } - - if !self.dont_check_compiler_stdout { - self.dont_check_compiler_stdout = config.parse_dont_check_compiler_stdout(ln); - } - - if !self.dont_check_compiler_stderr { - self.dont_check_compiler_stderr = config.parse_dont_check_compiler_stderr(ln); - } - - if !self.no_prefer_dynamic { - self.no_prefer_dynamic = config.parse_no_prefer_dynamic(ln); - } - - if !self.pretty_expanded { - self.pretty_expanded = config.parse_pretty_expanded(ln); - } - - if let Some(m) = config.parse_pretty_mode(ln) { - self.pretty_mode = m; - } - - if !self.pretty_compare_only { - self.pretty_compare_only = config.parse_pretty_compare_only(ln); - } - - if let Some(ab) = config.parse_aux_build(ln) { - self.aux_builds.push(ab); - } - - if let Some(ep) = config.parse_extern_private(ln) { - self.extern_private.push(ep); - } - - if let Some(ee) = config.parse_env(ln, "exec-env") { - self.exec_env.push(ee); - } - - if let Some(ee) = config.parse_env(ln, "rustc-env") { - self.rustc_env.push(ee); - } - - if let Some(ev) = config.parse_name_value_directive(ln, "unset-rustc-env") { - self.unset_rustc_env.push(ev); - } - - if let Some(cl) = config.parse_check_line(ln) { - self.check_lines.push(cl); - } - - if let Some(of) = config.parse_forbid_output(ln) { - self.forbid_output.push(of); - } - - if !self.check_test_line_numbers_match { - self.check_test_line_numbers_match = config.parse_check_test_line_numbers_match(ln); - } - - self.update_pass_mode(ln, cfg, config); - - if !self.ignore_pass { - self.ignore_pass = config.parse_ignore_pass(ln); - } - - if !self.disable_ui_testing_normalization { - self.disable_ui_testing_normalization = - config.parse_disable_ui_testing_normalization(ln); - } - - if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stdout") { - self.normalize_stdout.push(rule); - } - if let Some(rule) = config.parse_custom_normalization(ln, "normalize-stderr") { - self.normalize_stderr.push(rule); - } - - if let Some(code) = config.parse_failure_status(ln) { - self.failure_status = code; - } - - if !self.run_rustfix { - self.run_rustfix = config.parse_run_rustfix(ln); - } - - if !self.rustfix_only_machine_applicable { - self.rustfix_only_machine_applicable = - config.parse_rustfix_only_machine_applicable(ln); - } - - if self.assembly_output.is_none() { - self.assembly_output = config.parse_assembly_output(ln); - } - }); - - if self.failure_status == -1 { - self.failure_status = match config.mode { - Mode::RunFail => 101, - _ => 1, - }; - } - - for key in &["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] { - if let Ok(val) = env::var(key) { - if self.exec_env.iter().find(|&&(ref x, _)| x == key).is_none() { - self.exec_env.push(((*key).to_owned(), val)) - } - } - } - } - - fn update_pass_mode(&mut self, ln: &str, revision: Option<&str>, config: &Config) { - let check_no_run = |s| { - if config.mode != Mode::Ui && config.mode != Mode::Incremental { - panic!("`{}` header is only supported in UI and incremental tests", s); - } - if config.mode == Mode::Incremental && - !revision.map_or(false, |r| r.starts_with("cfail")) && - !self.revisions.iter().all(|r| r.starts_with("cfail")) { - panic!("`{}` header is only supported in `cfail` incremental tests", s); - } - }; - let pass_mode = if config.parse_name_directive(ln, "check-pass") { - check_no_run("check-pass"); - Some(PassMode::Check) - } else if config.parse_name_directive(ln, "build-pass") { - check_no_run("build-pass"); - Some(PassMode::Build) - } else if config.parse_name_directive(ln, "run-pass") { - if config.mode != Mode::Ui { - panic!("`run-pass` header is only supported in UI tests") - } - Some(PassMode::Run) - } else { - None - }; - match (self.pass_mode, pass_mode) { - (None, Some(_)) => self.pass_mode = pass_mode, - (Some(_), Some(_)) => panic!("multiple `*-pass` headers in a single test"), - (_, None) => {} - } - } - - pub fn pass_mode(&self, config: &Config) -> Option { - if !self.ignore_pass { - if let (mode @ Some(_), Some(_)) = (config.force_pass_mode, self.pass_mode) { - return mode; - } - } - self.pass_mode - } -} - -fn iter_header(testfile: &Path, cfg: Option<&str>, it: &mut dyn FnMut(&str)) { - if testfile.is_dir() { - return; - } - - let comment = if testfile.to_string_lossy().ends_with(".rs") { - "//" - } else { - "#" - }; - - // FIXME: would be nice to allow some whitespace between comment and brace :) - // It took me like 2 days to debug why compile-flags weren’t taken into account for my test :) - let comment_with_brace = comment.to_string() + "["; - - let rdr = BufReader::new(File::open(testfile).unwrap()); - for ln in rdr.lines() { - // Assume that any directives will be found before the first - // module or function. This doesn't seem to be an optimization - // with a warm page cache. Maybe with a cold one. - let ln = ln.unwrap(); - let ln = ln.trim(); - if ln.starts_with("fn") || ln.starts_with("mod") { - return; - } else if ln.starts_with(&comment_with_brace) { - // A comment like `//[foo]` is specific to revision `foo` - if let Some(close_brace) = ln.find(']') { - let open_brace = ln.find('[').unwrap(); - let lncfg = &ln[open_brace + 1 .. close_brace]; - let matches = match cfg { - Some(s) => s == &lncfg[..], - None => false, - }; - if matches { - it(ln[(close_brace + 1)..].trim_start()); - } - } else { - panic!("malformed condition directive: expected `{}foo]`, found `{}`", - comment_with_brace, ln) - } - } else if ln.starts_with(comment) { - it(ln[comment.len() ..].trim_start()); - } - } - return; -} - -impl Config { - fn parse_error_pattern(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "error-pattern") - } - - fn parse_forbid_output(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "forbid-output") - } - - fn parse_aux_build(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "aux-build") - .map(|r| r.trim().to_string()) - } - - fn parse_extern_private(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "extern-private") - } - - fn parse_compile_flags(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "compile-flags") - } - - fn parse_revisions(&self, line: &str) -> Option> { - self.parse_name_value_directive(line, "revisions") - .map(|r| r.split_whitespace().map(|t| t.to_string()).collect()) - } - - fn parse_run_flags(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "run-flags") - } - - fn parse_check_line(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "check") - } - - fn parse_force_host(&self, line: &str) -> bool { - self.parse_name_directive(line, "force-host") - } - - fn parse_build_aux_docs(&self, line: &str) -> bool { - self.parse_name_directive(line, "build-aux-docs") - } - - fn parse_check_stdout(&self, line: &str) -> bool { - self.parse_name_directive(line, "check-stdout") - } - - fn parse_dont_check_compiler_stdout(&self, line: &str) -> bool { - self.parse_name_directive(line, "dont-check-compiler-stdout") - } - - fn parse_dont_check_compiler_stderr(&self, line: &str) -> bool { - self.parse_name_directive(line, "dont-check-compiler-stderr") - } - - fn parse_no_prefer_dynamic(&self, line: &str) -> bool { - self.parse_name_directive(line, "no-prefer-dynamic") - } - - fn parse_pretty_expanded(&self, line: &str) -> bool { - self.parse_name_directive(line, "pretty-expanded") - } - - fn parse_pretty_mode(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "pretty-mode") - } - - fn parse_pretty_compare_only(&self, line: &str) -> bool { - self.parse_name_directive(line, "pretty-compare-only") - } - - fn parse_failure_status(&self, line: &str) -> Option { - match self.parse_name_value_directive(line, "failure-status") { - Some(code) => code.trim().parse::().ok(), - _ => None, - } - } - - fn parse_disable_ui_testing_normalization(&self, line: &str) -> bool { - self.parse_name_directive(line, "disable-ui-testing-normalization") - } - - fn parse_check_test_line_numbers_match(&self, line: &str) -> bool { - self.parse_name_directive(line, "check-test-line-numbers-match") - } - - fn parse_ignore_pass(&self, line: &str) -> bool { - self.parse_name_directive(line, "ignore-pass") - } - - fn parse_assembly_output(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "assembly-output") - .map(|r| r.trim().to_string()) - } - - fn parse_env(&self, line: &str, name: &str) -> Option<(String, String)> { - self.parse_name_value_directive(line, name).map(|nv| { - // nv is either FOO or FOO=BAR - let mut strs: Vec = nv.splitn(2, '=').map(str::to_owned).collect(); - - match strs.len() { - 1 => (strs.pop().unwrap(), String::new()), - 2 => { - let end = strs.pop().unwrap(); - (strs.pop().unwrap(), end) - } - n => panic!("Expected 1 or 2 strings, not {}", n), - } - }) - } - - fn parse_pp_exact(&self, line: &str, testfile: &Path) -> Option { - if let Some(s) = self.parse_name_value_directive(line, "pp-exact") { - Some(PathBuf::from(&s)) - } else if self.parse_name_directive(line, "pp-exact") { - testfile.file_name().map(PathBuf::from) - } else { - None - } - } - - fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)> { - if self.parse_cfg_name_directive(line, prefix) == ParsedNameDirective::Match { - let from = parse_normalization_string(&mut line)?; - let to = parse_normalization_string(&mut line)?; - Some((from, to)) - } else { - None - } - } - - fn parse_needs_matching_clang(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-matching-clang") - } - - fn parse_needs_profiler_support(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-profiler-support") - } - - fn parse_needs_sanitizer_support(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-sanitizer-support") - } - - /// Parses a name-value directive which contains config-specific information, e.g., `ignore-x86` - /// or `normalize-stderr-32bit`. - fn parse_cfg_name_directive(&self, line: &str, prefix: &str) -> ParsedNameDirective { - if line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-') { - let name = line[prefix.len() + 1..] - .split(&[':', ' '][..]) - .next() - .unwrap(); - - if name == "test" || - util::matches_os(&self.target, name) || // target - name == util::get_arch(&self.target) || // architecture - name == util::get_pointer_width(&self.target) || // pointer width - name == self.stage_id.split('-').next().unwrap() || // stage - Some(name) == util::get_env(&self.target) || // env - (self.target != self.host && name == "cross-compile") || - match self.compare_mode { - Some(CompareMode::Nll) => name == "compare-mode-nll", - Some(CompareMode::Polonius) => name == "compare-mode-polonius", - None => false, - } || - (cfg!(debug_assertions) && name == "debug") { - ParsedNameDirective::Match - } else { - match self.mode { - common::DebugInfoGdbLldb => { - if name == "gdb" { - ParsedNameDirective::MatchGdb - } else if name == "lldb" { - ParsedNameDirective::MatchLldb - } else { - ParsedNameDirective::NoMatch - } - }, - common::DebugInfoCdb => if name == "cdb" { - ParsedNameDirective::Match - } else { - ParsedNameDirective::NoMatch - }, - common::DebugInfoGdb => if name == "gdb" { - ParsedNameDirective::Match - } else { - ParsedNameDirective::NoMatch - }, - common::DebugInfoLldb => if name == "lldb" { - ParsedNameDirective::Match - } else { - ParsedNameDirective::NoMatch - }, - common::Pretty => if name == "pretty" { - ParsedNameDirective::Match - } else { - ParsedNameDirective::NoMatch - }, - _ => ParsedNameDirective::NoMatch, - } - } - } else { - ParsedNameDirective::NoMatch - } - } - - fn has_cfg_prefix(&self, line: &str, prefix: &str) -> bool { - // returns whether this line contains this prefix or not. For prefix - // "ignore", returns true if line says "ignore-x86_64", "ignore-arch", - // "ignore-android" etc. - line.starts_with(prefix) && line.as_bytes().get(prefix.len()) == Some(&b'-') - } - - fn parse_name_directive(&self, line: &str, directive: &str) -> bool { - // Ensure the directive is a whole word. Do not match "ignore-x86" when - // the line says "ignore-x86_64". - line.starts_with(directive) && match line.as_bytes().get(directive.len()) { - None | Some(&b' ') | Some(&b':') => true, - _ => false, - } - } - - pub fn parse_name_value_directive(&self, line: &str, directive: &str) -> Option { - let colon = directive.len(); - if line.starts_with(directive) && line.as_bytes().get(colon) == Some(&b':') { - let value = line[(colon + 1)..].to_owned(); - debug!("{}: {}", directive, value); - Some(expand_variables(value, self)) - } else { - None - } - } - - pub fn find_rust_src_root(&self) -> Option { - let mut path = self.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); - } - } - - None - } - - fn parse_run_rustfix(&self, line: &str) -> bool { - self.parse_name_directive(line, "run-rustfix") - } - - fn parse_rustfix_only_machine_applicable(&self, line: &str) -> bool { - self.parse_name_directive(line, "rustfix-only-machine-applicable") - } - - fn parse_edition(&self, line: &str) -> Option { - self.parse_name_value_directive(line, "edition") - } -} - -pub fn lldb_version_to_int(version_string: &str) -> isize { - let error_string = format!( - "Encountered LLDB version string with unexpected format: {}", - version_string - ); - version_string.parse().expect(&error_string) -} - -fn expand_variables(mut value: String, config: &Config) -> String { - const CWD: &'static str = "{{cwd}}"; - const SRC_BASE: &'static str = "{{src-base}}"; - const BUILD_BASE: &'static str = "{{build-base}}"; - - if value.contains(CWD) { - let cwd = env::current_dir().unwrap(); - value = value.replace(CWD, &cwd.to_string_lossy()); - } - - if value.contains(SRC_BASE) { - value = value.replace(SRC_BASE, &config.src_base.to_string_lossy()); - } - - if value.contains(BUILD_BASE) { - value = value.replace(BUILD_BASE, &config.build_base.to_string_lossy()); - } - - value -} - -/// Finds the next quoted string `"..."` in `line`, and extract the content from it. Move the `line` -/// variable after the end of the quoted string. -/// -/// # Examples -/// -/// ``` -/// let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)\"."; -/// let first = parse_normalization_string(&mut s); -/// assert_eq!(first, Some("something (32 bits)".to_owned())); -/// assert_eq!(s, " -> \"something ($WORD bits)\"."); -/// ``` -fn parse_normalization_string(line: &mut &str) -> Option { - // FIXME support escapes in strings. - let begin = line.find('"')? + 1; - let end = line[begin..].find('"')? + begin; - let result = line[begin..end].to_owned(); - *line = &line[end + 1..]; - Some(result) -} diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs deleted file mode 100644 index 2a1831d5ee80c..0000000000000 --- a/src/tools/compiletest/src/header/tests.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::*; - -#[test] -fn test_parse_normalization_string() { - let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)\"."; - let first = parse_normalization_string(&mut s); - assert_eq!(first, Some("something (32 bits)".to_owned())); - assert_eq!(s, " -> \"something ($WORD bits)\"."); - - // Nothing to normalize (No quotes) - let mut s = "normalize-stderr-32bit: something (32 bits) -> something ($WORD bits)."; - let first = parse_normalization_string(&mut s); - assert_eq!(first, None); - assert_eq!(s, r#"normalize-stderr-32bit: something (32 bits) -> something ($WORD bits)."#); - - // Nothing to normalize (Only a single quote) - let mut s = "normalize-stderr-32bit: \"something (32 bits) -> something ($WORD bits)."; - let first = parse_normalization_string(&mut s); - assert_eq!(first, None); - assert_eq!(s, "normalize-stderr-32bit: \"something (32 bits) -> something ($WORD bits)."); - - // Nothing to normalize (Three quotes) - let mut s = "normalize-stderr-32bit: \"something (32 bits)\" -> \"something ($WORD bits)."; - let first = parse_normalization_string(&mut s); - assert_eq!(first, Some("something (32 bits)".to_owned())); - assert_eq!(s, " -> \"something ($WORD bits)."); -} diff --git a/src/tools/compiletest/src/json.rs b/src/tools/compiletest/src/json.rs deleted file mode 100644 index 02b09e21ff022..0000000000000 --- a/src/tools/compiletest/src/json.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! These structs are a subset of the ones found in `syntax::json`. -//! They are only used for deserialization of JSON output provided by libtest. - -use crate::errors::{Error, ErrorKind}; -use crate::runtest::ProcRes; -use serde::Deserialize; -use serde_json; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -#[derive(Deserialize)] -struct Diagnostic { - message: String, - code: Option, - level: String, - spans: Vec, - children: Vec, - rendered: Option, -} - -#[derive(Deserialize)] -struct ArtifactNotification { - #[allow(dead_code)] - artifact: PathBuf, -} - -#[derive(Deserialize, Clone)] -struct DiagnosticSpan { - file_name: String, - line_start: usize, - line_end: usize, - column_start: usize, - column_end: usize, - is_primary: bool, - label: Option, - suggested_replacement: Option, - expansion: Option>, -} - -impl DiagnosticSpan { - /// Returns the deepest source span in the macro call stack with a given file name. - /// This is either the supplied span, or the span for some macro callsite that expanded to it. - fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan { - if self.file_name == file_name { - self - } else { - self.expansion - .as_ref() - .map(|origin| origin.span.first_callsite_in_file(file_name)) - .unwrap_or(self) - } - } -} - -#[derive(Deserialize, Clone)] -struct DiagnosticSpanMacroExpansion { - /// span where macro was applied to generate this code - span: DiagnosticSpan, - - /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") - macro_decl_name: String, -} - -#[derive(Deserialize, Clone)] -struct DiagnosticCode { - /// The code itself. - code: String, - /// An explanation for the code. - explanation: Option, -} - -pub fn extract_rendered(output: &str) -> String { - output - .lines() - .filter_map(|line| { - if line.starts_with('{') { - if let Ok(diagnostic) = serde_json::from_str::(line) { - diagnostic.rendered - } else if let Ok(_) = serde_json::from_str::(line) { - // Ignore the notification. - None - } else { - print!( - "failed to decode compiler output as json: line: {}\noutput: {}", - line, output - ); - panic!() - } - } else { - // preserve non-JSON lines, such as ICEs - Some(format!("{}\n", line)) - } - }) - .collect() -} - -pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec { - output - .lines() - .flat_map(|line| parse_line(file_name, line, output, proc_res)) - .collect() -} - -fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec { - // The compiler sometimes intermingles non-JSON stuff into the - // output. This hack just skips over such lines. Yuck. - if line.starts_with('{') { - match serde_json::from_str::(line) { - Ok(diagnostic) => { - let mut expected_errors = vec![]; - push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name); - expected_errors - } - Err(error) => { - proc_res.fatal(Some(&format!( - "failed to decode compiler output as json: \ - `{}`\nline: {}\noutput: {}", - error, line, output - ))); - } - } - } else { - vec![] - } -} - -fn push_expected_errors( - expected_errors: &mut Vec, - diagnostic: &Diagnostic, - default_spans: &[&DiagnosticSpan], - file_name: &str, -) { - // In case of macro expansions, we need to get the span of the callsite - let spans_info_in_this_file: Vec<_> = diagnostic - .spans - .iter() - .map(|span| (span.is_primary, span.first_callsite_in_file(file_name))) - .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name)) - .collect(); - - let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter() - .map(|(_, span)| span) - .collect(); - - let primary_spans: Vec<_> = spans_info_in_this_file.iter() - .filter(|(is_primary, _)| *is_primary) - .map(|(_, span)| span) - .take(1) // sometimes we have more than one showing up in the json; pick first - .cloned() - .collect(); - let primary_spans = if primary_spans.is_empty() { - // subdiagnostics often don't have a span of their own; - // inherit the span from the parent in that case - default_spans - } else { - &primary_spans - }; - - // We break the output into multiple lines, and then append the - // [E123] to every line in the output. This may be overkill. The - // intention was to match existing tests that do things like "//| - // found `i32` [E123]" and expect to match that somewhere, and yet - // also ensure that `//~ ERROR E123` *always* works. The - // assumption is that these multi-line error messages are on their - // way out anyhow. - let with_code = |span: &DiagnosticSpan, text: &str| { - match diagnostic.code { - Some(ref code) => - // FIXME(#33000) -- it'd be better to use a dedicated - // UI harness than to include the line/col number like - // this, but some current tests rely on it. - // - // Note: Do NOT include the filename. These can easily - // cause false matches where the expected message - // appears in the filename, and hence the message - // changes but the test still passes. - format!("{}:{}: {}:{}: {} [{}]", - span.line_start, span.column_start, - span.line_end, span.column_end, - text, code.code.clone()), - None => - // FIXME(#33000) -- it'd be better to use a dedicated UI harness - format!("{}:{}: {}:{}: {}", - span.line_start, span.column_start, - span.line_end, span.column_end, - text), - } - }; - - // Convert multi-line messages into multiple expected - // errors. We expect to replace these with something - // more structured shortly anyhow. - let mut message_lines = diagnostic.message.lines(); - if let Some(first_line) = message_lines.next() { - for span in primary_spans { - let msg = with_code(span, first_line); - let kind = ErrorKind::from_str(&diagnostic.level).ok(); - expected_errors.push(Error { - line_num: span.line_start, - kind, - msg, - }); - } - } - for next_line in message_lines { - for span in primary_spans { - expected_errors.push(Error { - line_num: span.line_start, - kind: None, - msg: with_code(span, next_line), - }); - } - } - - // If the message has a suggestion, register that. - for span in primary_spans { - if let Some(ref suggested_replacement) = span.suggested_replacement { - for (index, line) in suggested_replacement.lines().enumerate() { - expected_errors.push(Error { - line_num: span.line_start + index, - kind: Some(ErrorKind::Suggestion), - msg: line.to_string(), - }); - } - } - } - - // Add notes for the backtrace - for span in primary_spans { - for frame in &span.expansion { - push_backtrace(expected_errors, frame, file_name); - } - } - - // Add notes for any labels that appear in the message. - for span in spans_in_this_file - .iter() - .filter(|span| span.label.is_some()) - { - expected_errors.push(Error { - line_num: span.line_start, - kind: Some(ErrorKind::Note), - msg: span.label.clone().unwrap(), - }); - } - - // Flatten out the children. - for child in &diagnostic.children { - push_expected_errors(expected_errors, child, primary_spans, file_name); - } -} - -fn push_backtrace( - expected_errors: &mut Vec, - expansion: &DiagnosticSpanMacroExpansion, - file_name: &str, -) { - if Path::new(&expansion.span.file_name) == Path::new(&file_name) { - expected_errors.push(Error { - line_num: expansion.span.line_start, - kind: Some(ErrorKind::Note), - msg: format!("in this expansion of {}", expansion.macro_decl_name), - }); - } - - for previous_expansion in &expansion.span.expansion { - push_backtrace(expected_errors, previous_expansion, file_name); - } -} diff --git a/src/tools/compiletest/src/main.rs b/src/tools/compiletest/src/main.rs deleted file mode 100644 index bde49ff391c8c..0000000000000 --- a/src/tools/compiletest/src/main.rs +++ /dev/null @@ -1,1098 +0,0 @@ -#![crate_name = "compiletest"] -#![feature(test)] -#![feature(vec_remove_item)] - -extern crate test; - -use crate::common::{CompareMode, PassMode}; -use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; -use crate::common::{Config, TestPaths}; -use crate::common::{DebugInfoCdb, DebugInfoGdbLldb, DebugInfoGdb, DebugInfoLldb, Mode, Pretty}; -use getopts::Options; -use std::env; -use std::ffi::OsString; -use std::fs; -use std::io::{self, ErrorKind}; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::time::SystemTime; -use test::ColorConfig; -use crate::util::logv; -use walkdir::WalkDir; -use env_logger; -use getopts; -use log::*; - -use self::header::{EarlyProps, Ignore}; - -#[cfg(test)] -mod tests; - -pub mod common; -pub mod errors; -pub mod header; -mod json; -mod raise_fd_limit; -mod read2; -pub mod runtest; -pub mod util; - -fn main() { - env_logger::init(); - - let config = parse_config(env::args().collect()); - - if config.valgrind_path.is_none() && config.force_valgrind { - panic!("Can't find Valgrind to run Valgrind tests"); - } - - log_config(&config); - run_tests(&config); -} - -pub fn parse_config(args: Vec) -> Config { - let mut opts = Options::new(); - opts.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( - "", - "rustdoc-path", - "path to rustdoc to use for compiling", - "PATH", - ) - .reqopt( - "", - "lldb-python", - "path to python to use for doc tests", - "PATH", - ) - .reqopt( - "", - "docck-python", - "path to python to use for doc tests", - "PATH", - ) - .optopt( - "", - "valgrind-path", - "path to Valgrind executable for Valgrind tests", - "PROGRAM", - ) - .optflag( - "", - "force-valgrind", - "fail if Valgrind tests cannot be run under Valgrind", - ) - .optopt( - "", - "run-clang-based-tests-with", - "path to Clang executable", - "PATH", - ) - .optopt( - "", - "llvm-filecheck", - "path to LLVM's FileCheck binary", - "DIR", - ) - .reqopt("", "src-base", "directory to scan for test files", "PATH") - .reqopt( - "", - "build-base", - "directory to deposit test outputs", - "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-valgrind|pretty|debug-info|incremental|mir-opt)", - ) - .optopt( - "", - "pass", - "force {check,build,run}-pass tests to this mode.", - "check | build | run" - ) - .optflag("", "ignored", "run tests marked as ignored") - .optflag("", "exact", "filters match exactly") - .optopt( - "", - "runtool", - "supervisor program to run tests under \ - (eg. emulator, valgrind)", - "PROGRAM", - ) - .optopt( - "", - "host-rustcflags", - "flags to pass to rustc for host", - "FLAGS", - ) - .optopt( - "", - "target-rustcflags", - "flags to pass to rustc for target", - "FLAGS", - ) - .optflag("", "verbose", "run tests verbosely, showing all output") - .optflag( - "", - "bless", - "overwrite stderr/stdout files instead of complaining about a mismatch", - ) - .optflag( - "", - "quiet", - "print one character per test instead of one line", - ) - .optopt("", "color", "coloring: auto, always, never", "WHEN") - .optopt("", "logfile", "file to log test execution to", "FILE") - .optopt("", "target", "the target to build for", "TARGET") - .optopt("", "host", "the host to build for", "HOST") - .optopt( - "", - "cdb", - "path to CDB to use for CDB debuginfo tests", - "PATH", - ) - .optopt( - "", - "gdb", - "path to GDB to use for GDB debuginfo tests", - "PATH", - ) - .optopt( - "", - "lldb-version", - "the version of LLDB used", - "VERSION STRING", - ) - .optopt( - "", - "llvm-version", - "the version of LLVM used", - "VERSION STRING", - ) - .optflag("", "system-llvm", "is LLVM the system LLVM") - .optopt( - "", - "android-cross-path", - "Android NDK standalone path", - "PATH", - ) - .optopt("", "adb-path", "path to the android debugger", "PATH") - .optopt( - "", - "adb-test-dir", - "path to tests for the android debugger", - "PATH", - ) - .optopt( - "", - "lldb-python-dir", - "directory containing LLDB's python module", - "PATH", - ) - .reqopt("", "cc", "path to a C compiler", "PATH") - .reqopt("", "cxx", "path to a C++ compiler", "PATH") - .reqopt("", "cflags", "flags for the C compiler", "FLAGS") - .optopt("", "ar", "path to an archiver", "PATH") - .optopt("", "linker", "path to a linker", "PATH") - .reqopt( - "", - "llvm-components", - "list of LLVM components built in", - "LIST", - ) - .reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS") - .optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH") - .optopt("", "nodejs", "the name of nodejs", "PATH") - .optopt( - "", - "remote-test-client", - "path to the remote test client", - "PATH", - ) - .optopt( - "", - "compare-mode", - "mode describing what file the actual ui output will be compared to", - "COMPARE MODE", - ) - .optflag( - "", - "rustfix-coverage", - "enable this to generate a Rustfix coverage file, which is saved in \ - `.//rustfix_missing_coverage.txt`", - ) - .optflag("h", "help", "show this message"); - - let (argv0, args_) = args.split_first().unwrap(); - if args.len() == 1 || args[1] == "-h" || args[1] == "--help" { - let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); - println!("{}", opts.usage(&message)); - println!(""); - panic!() - } - - let matches = &match opts.parse(args_) { - Ok(m) => m, - Err(f) => panic!("{:?}", f), - }; - - if matches.opt_present("h") || matches.opt_present("help") { - let message = format!("Usage: {} [OPTIONS] [TESTNAME...]", argv0); - println!("{}", opts.usage(&message)); - println!(""); - panic!() - } - - fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf { - match m.opt_str(nm) { - Some(s) => PathBuf::from(&s), - None => panic!("no option (=path) found for {}", nm), - } - } - - fn make_absolute(path: PathBuf) -> PathBuf { - if path.is_relative() { - env::current_dir().unwrap().join(path) - } else { - path - } - } - - let target = opt_str2(matches.opt_str("target")); - let android_cross_path = opt_path(matches, "android-cross-path"); - let cdb = analyze_cdb(matches.opt_str("cdb"), &target); - let (gdb, gdb_version, gdb_native_rust) = analyze_gdb(matches.opt_str("gdb"), &target, - &android_cross_path); - let (lldb_version, lldb_native_rust) = extract_lldb_version(matches.opt_str("lldb-version")); - - let color = match matches.opt_str("color").as_ref().map(|x| &**x) { - Some("auto") | None => ColorConfig::AutoColor, - Some("always") => ColorConfig::AlwaysColor, - Some("never") => ColorConfig::NeverColor, - Some(x) => panic!( - "argument for --color must be auto, always, or never, but found `{}`", - x - ), - }; - - let src_base = opt_path(matches, "src-base"); - let run_ignored = matches.opt_present("ignored"); - Config { - bless: matches.opt_present("bless"), - compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")), - run_lib_path: make_absolute(opt_path(matches, "run-lib-path")), - rustc_path: opt_path(matches, "rustc-path"), - rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from), - lldb_python: matches.opt_str("lldb-python").unwrap(), - docck_python: matches.opt_str("docck-python").unwrap(), - valgrind_path: matches.opt_str("valgrind-path"), - force_valgrind: matches.opt_present("force-valgrind"), - run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"), - llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from), - llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from), - src_base, - build_base: opt_path(matches, "build-base"), - stage_id: matches.opt_str("stage-id").unwrap(), - mode: matches - .opt_str("mode") - .unwrap() - .parse() - .expect("invalid mode"), - run_ignored, - filter: matches.free.first().cloned(), - filter_exact: matches.opt_present("exact"), - force_pass_mode: matches.opt_str("pass").map(|mode| - mode.parse::() - .unwrap_or_else(|_| panic!("unknown `--pass` option `{}` given", mode)) - ), - logfile: matches.opt_str("logfile").map(|s| PathBuf::from(&s)), - runtool: matches.opt_str("runtool"), - host_rustcflags: matches.opt_str("host-rustcflags"), - target_rustcflags: matches.opt_str("target-rustcflags"), - target: target, - host: opt_str2(matches.opt_str("host")), - cdb, - gdb, - gdb_version, - gdb_native_rust, - lldb_version, - lldb_native_rust, - llvm_version: matches.opt_str("llvm-version"), - system_llvm: matches.opt_present("system-llvm"), - android_cross_path: android_cross_path, - adb_path: opt_str2(matches.opt_str("adb-path")), - adb_test_dir: opt_str2(matches.opt_str("adb-test-dir")), - adb_device_status: opt_str2(matches.opt_str("target")).contains("android") - && "(none)" != opt_str2(matches.opt_str("adb-test-dir")) - && !opt_str2(matches.opt_str("adb-test-dir")).is_empty(), - lldb_python_dir: matches.opt_str("lldb-python-dir"), - verbose: matches.opt_present("verbose"), - quiet: matches.opt_present("quiet"), - color, - remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from), - compare_mode: matches.opt_str("compare-mode").map(CompareMode::parse), - rustfix_coverage: matches.opt_present("rustfix-coverage"), - - cc: matches.opt_str("cc").unwrap(), - cxx: matches.opt_str("cxx").unwrap(), - cflags: matches.opt_str("cflags").unwrap(), - ar: matches.opt_str("ar").unwrap_or("ar".into()), - linker: matches.opt_str("linker"), - llvm_components: matches.opt_str("llvm-components").unwrap(), - llvm_cxxflags: matches.opt_str("llvm-cxxflags").unwrap(), - nodejs: matches.opt_str("nodejs"), - } -} - -pub fn log_config(config: &Config) { - let c = config; - logv(c, "configuration:".to_string()); - 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.display())); - logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path)); - logv(c, format!("src_base: {:?}", config.src_base.display())); - logv(c, format!("build_base: {:?}", config.build_base.display())); - logv(c, format!("stage_id: {}", config.stage_id)); - logv(c, format!("mode: {}", config.mode)); - logv(c, format!("run_ignored: {}", config.run_ignored)); - logv( - c, - format!( - "filter: {}", - opt_str(&config.filter.as_ref().map(|re| re.to_owned())) - ), - ); - logv(c, format!("filter_exact: {}", config.filter_exact)); - logv(c, format!( - "force_pass_mode: {}", - opt_str(&config.force_pass_mode.map(|m| format!("{}", m))), - )); - logv(c, format!("runtool: {}", opt_str(&config.runtool))); - logv( - c, - format!("host-rustcflags: {}", opt_str(&config.host_rustcflags)), - ); - logv( - c, - format!("target-rustcflags: {}", opt_str(&config.target_rustcflags)), - ); - logv(c, format!("target: {}", config.target)); - logv(c, format!("host: {}", config.host)); - logv( - c, - format!( - "android-cross-path: {:?}", - config.android_cross_path.display() - ), - ); - 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), - ); - logv(c, format!("ar: {}", config.ar)); - logv(c, format!("linker: {:?}", config.linker)); - logv(c, format!("verbose: {}", config.verbose)); - logv(c, format!("quiet: {}", config.quiet)); - logv(c, "\n".to_string()); -} - -pub fn opt_str(maybestr: &Option) -> &str { - match *maybestr { - None => "(none)", - Some(ref s) => s, - } -} - -pub fn opt_str2(maybestr: Option) -> String { - match maybestr { - None => "(none)".to_owned(), - Some(s) => s, - } -} - -pub fn run_tests(config: &Config) { - if config.target.contains("android") { - if config.mode == DebugInfoGdb || config.mode == DebugInfoGdbLldb { - println!( - "{} debug-info test uses tcp 5039 port.\ - please reserve it", - config.target - ); - - // android debug-info test uses remote debugger so, we test 1 thread - // at once as they're all sharing the same TCP port to communicate - // over. - // - // we should figure out how to lift this restriction! (run them all - // on different ports allocated dynamically). - env::set_var("RUST_TEST_THREADS", "1"); - } - } - - match config.mode { - // Note that we don't need to emit the gdb warning when - // DebugInfoGdbLldb, so it is ok to list that here. - DebugInfoGdbLldb | DebugInfoLldb => { - if let Some(lldb_version) = config.lldb_version.as_ref() { - if is_blacklisted_lldb_version(&lldb_version[..]) { - println!( - "WARNING: The used version of LLDB ({}) has a \ - known issue that breaks debuginfo tests. See \ - issue #32520 for more information. Skipping all \ - LLDB-based tests!", - lldb_version - ); - return; - } - } - - // Some older versions of LLDB seem to have problems with multiple - // instances running in parallel, so only run one test thread at a - // time. - env::set_var("RUST_TEST_THREADS", "1"); - } - - DebugInfoGdb => { - if config.remote_test_client.is_some() && !config.target.contains("android") { - println!( - "WARNING: debuginfo tests are not available when \ - testing with remote" - ); - return; - } - } - - DebugInfoCdb | _ => { /* proceed */ } - } - - // FIXME(#33435) Avoid spurious failures in codegen-units/partitioning tests. - if let Mode::CodegenUnits = config.mode { - let _ = fs::remove_dir_all("tmp/partitioning-tests"); - } - - // If we want to collect rustfix coverage information, - // we first make sure that the coverage file does not exist. - // It will be created later on. - if config.rustfix_coverage { - let mut coverage_file_path = config.build_base.clone(); - coverage_file_path.push("rustfix_missing_coverage.txt"); - if coverage_file_path.exists() { - if let Err(e) = fs::remove_file(&coverage_file_path) { - panic!("Could not delete {} due to {}", coverage_file_path.display(), e) - } - } - } - - 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 - unsafe { - raise_fd_limit::raise_fd_limit(); - } - // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows - // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary - env::set_var("__COMPAT_LAYER", "RunAsInvoker"); - - // Let tests know which target they're running as - env::set_var("TARGET", &config.target); - - let res = test::run_tests_console(&opts, tests); - match res { - Ok(true) => {} - Ok(false) => panic!("Some tests failed"), - Err(e) => { - println!("I/O failure during tests: {:?}", e); - } - } -} - -pub fn test_opts(config: &Config) -> test::TestOpts { - test::TestOpts { - exclude_should_panic: false, - filter: config.filter.clone(), - filter_exact: config.filter_exact, - run_ignored: if config.run_ignored { - test::RunIgnored::Yes - } else { - test::RunIgnored::No - }, - format: if config.quiet { - test::OutputFormat::Terse - } else { - test::OutputFormat::Pretty - }, - logfile: config.logfile.clone(), - run_tests: true, - bench_benchmarks: true, - nocapture: match env::var("RUST_TEST_NOCAPTURE") { - Ok(val) => &val != "0", - Err(_) => false, - }, - color: config.color, - test_threads: None, - skip: vec![], - list: false, - options: test::Options::new(), - } -} - -pub fn make_tests(config: &Config) -> Vec { - debug!("making tests from {:?}", config.src_base.display()); - let mut tests = Vec::new(); - collect_tests_from_dir( - config, - &config.src_base, - &config.src_base, - &PathBuf::new(), - &mut tests, - ).unwrap(); - tests -} - -fn collect_tests_from_dir( - config: &Config, - base: &Path, - dir: &Path, - relative_dir_path: &Path, - tests: &mut Vec, -) -> io::Result<()> { - // Ignore directories that contain a file named `compiletest-ignore-dir`. - if dir.join("compiletest-ignore-dir").exists() { - return Ok(()); - } - - if config.mode == Mode::RunMake && dir.join("Makefile").exists() { - let paths = TestPaths { - file: dir.to_path_buf(), - relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), - }; - tests.extend(make_test(config, &paths)); - return Ok(()); - } - - // If we find a test foo/bar.rs, we have to build the - // output directory `$build/foo` so we can write - // `$build/foo/bar` into it. We do this *now* in this - // sequential loop because otherwise, if we do it in the - // tests themselves, they race for the privilege of - // creating the directories and sometimes fail randomly. - let build_dir = output_relative_path(config, relative_dir_path); - fs::create_dir_all(&build_dir).unwrap(); - - // Add each `.rs` file as a test, and recurse further on any - // subdirectories we find, except for `aux` directories. - for file in fs::read_dir(dir)? { - let file = file?; - let file_path = file.path(); - let file_name = file.file_name(); - if is_test(&file_name) { - debug!("found test file: {:?}", file_path.display()); - let paths = TestPaths { - file: file_path, - relative_dir: relative_dir_path.to_path_buf(), - }; - tests.extend(make_test(config, &paths)) - } else if file_path.is_dir() { - let relative_file_path = relative_dir_path.join(file.file_name()); - if &file_name != "auxiliary" { - debug!("found directory: {:?}", file_path.display()); - collect_tests_from_dir(config, base, &file_path, &relative_file_path, tests)?; - } - } else { - debug!("found other file/directory: {:?}", file_path.display()); - } - } - Ok(()) -} - - -/// Returns true if `file_name` looks like a proper test file name. -pub fn is_test(file_name: &OsString) -> bool { - let file_name = file_name.to_str().unwrap(); - - if !file_name.ends_with(".rs") { - return false; - } - - // `.`, `#`, and `~` are common temp-file prefixes. - let invalid_prefixes = &[".", "#", "~"]; - !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) -} - -pub fn make_test(config: &Config, testpaths: &TestPaths) -> Vec { - let early_props = if config.mode == Mode::RunMake { - // Allow `ignore` directives to be in the Makefile. - EarlyProps::from_file(config, &testpaths.file.join("Makefile")) - } else { - EarlyProps::from_file(config, &testpaths.file) - }; - - // The `should-fail` annotation doesn't apply to pretty tests, - // since we run the pretty printer across all tests by default. - // If desired, we could add a `should-fail-pretty` annotation. - let should_panic = match config.mode { - Pretty => test::ShouldPanic::No, - _ => if early_props.should_fail { - test::ShouldPanic::Yes - } else { - test::ShouldPanic::No - }, - }; - - // Incremental tests are special, they inherently cannot be run in parallel. - // `runtest::run` will be responsible for iterating over revisions. - let revisions = if early_props.revisions.is_empty() || config.mode == Mode::Incremental { - vec![None] - } else { - early_props.revisions.iter().map(|r| Some(r)).collect() - }; - revisions - .into_iter() - .map(|revision| { - // Debugging emscripten code doesn't make sense today - let ignore = early_props.ignore == Ignore::Ignore - || !up_to_date( - config, - testpaths, - &early_props, - revision.map(|s| s.as_str()), - ) - || ((config.mode == DebugInfoGdbLldb || config.mode == DebugInfoCdb || - config.mode == DebugInfoGdb || config.mode == DebugInfoLldb) - && config.target.contains("emscripten")) - || (config.mode == DebugInfoGdb && !early_props.ignore.can_run_gdb()) - || (config.mode == DebugInfoLldb && !early_props.ignore.can_run_lldb()); - test::TestDescAndFn { - desc: test::TestDesc { - name: make_test_name(config, testpaths, revision), - ignore, - should_panic, - allow_fail: false, - }, - testfn: make_test_closure(config, early_props.ignore, testpaths, revision), - } - }) - .collect() -} - -fn stamp(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf { - output_base_dir(config, testpaths, revision).join("stamp") -} - -fn up_to_date( - config: &Config, - testpaths: &TestPaths, - props: &EarlyProps, - revision: Option<&str>, -) -> bool { - let stamp_name = stamp(config, testpaths, revision); - // Check hash. - let contents = match fs::read_to_string(&stamp_name) { - Ok(f) => f, - Err(ref e) if e.kind() == ErrorKind::InvalidData => panic!("Can't read stamp contents"), - Err(_) => return true, - }; - let expected_hash = runtest::compute_stamp_hash(config); - if contents != expected_hash { - return true; - } - - // Check timestamps. - let rust_src_dir = config - .find_rust_src_root() - .expect("Could not find Rust source root"); - let stamp = Stamp::from_path(&stamp_name); - let mut inputs = vec![Stamp::from_path(&testpaths.file), Stamp::from_path(&config.rustc_path)]; - inputs.extend( - props - .aux - .iter() - .map(|aux| { - Stamp::from_path(&testpaths.file.parent().unwrap().join("auxiliary").join(aux)) - }), - ); - // Relevant pretty printer files - let pretty_printer_files = [ - "src/etc/debugger_pretty_printers_common.py", - "src/etc/gdb_load_rust_pretty_printers.py", - "src/etc/gdb_rust_pretty_printing.py", - "src/etc/lldb_batchmode.py", - "src/etc/lldb_rust_formatters.py", - ]; - inputs.extend(pretty_printer_files.iter().map(|pretty_printer_file| { - Stamp::from_path(&rust_src_dir.join(pretty_printer_file)) - })); - inputs.extend(Stamp::from_dir(&config.run_lib_path)); - if let Some(ref rustdoc_path) = config.rustdoc_path { - inputs.push(Stamp::from_path(&rustdoc_path)); - inputs.push(Stamp::from_path(&rust_src_dir.join("src/etc/htmldocck.py"))); - } - - // UI test files. - inputs.extend(UI_EXTENSIONS.iter().map(|extension| { - let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension); - Stamp::from_path(path) - })); - - // Compiletest itself. - inputs.extend(Stamp::from_dir(&rust_src_dir.join("src/tools/compiletest/"))); - - inputs.iter().any(|input| input > &stamp) -} - -#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)] -struct Stamp { - time: SystemTime, - file: PathBuf, -} - -impl Stamp { - fn from_path(p: &Path) -> Self { - let time = fs::metadata(p) - .and_then(|metadata| metadata.modified()) - .unwrap_or(SystemTime::UNIX_EPOCH); - - Stamp { - time, - file: p.into(), - } - } - - fn from_dir(path: &Path) -> impl Iterator { - WalkDir::new(path) - .into_iter() - .map(|entry| entry.unwrap()) - .filter(|entry| entry.file_type().is_file()) - .map(|entry| { - let time = (|| -> io::Result<_> { entry.metadata()?.modified() })(); - - Stamp { - time: time.unwrap_or(SystemTime::UNIX_EPOCH), - file: entry.path().into(), - } - }) - } -} - -fn make_test_name( - config: &Config, - testpaths: &TestPaths, - revision: Option<&String>, -) -> test::TestName { - // Convert a complete path to something like - // - // ui/foo/bar/baz.rs - let path = PathBuf::from(config.src_base.file_name().unwrap()) - .join(&testpaths.relative_dir) - .join(&testpaths.file.file_name().unwrap()); - let mode_suffix = match config.compare_mode { - Some(ref mode) => format!(" ({})", mode.to_str()), - None => String::new(), - }; - test::DynTestName(format!( - "[{}{}] {}{}", - config.mode, - mode_suffix, - path.display(), - revision.map_or("".to_string(), |rev| format!("#{}", rev)) - )) -} - -fn make_test_closure( - config: &Config, - ignore: Ignore, - testpaths: &TestPaths, - revision: Option<&String>, -) -> test::TestFn { - let mut config = config.clone(); - if config.mode == DebugInfoGdbLldb { - // If both gdb and lldb were ignored, then the test as a whole - // would be ignored. - if !ignore.can_run_gdb() { - config.mode = DebugInfoLldb; - } else if !ignore.can_run_lldb() { - config.mode = DebugInfoGdb; - } - } - - let testpaths = testpaths.clone(); - let revision = revision.cloned(); - test::DynTestFn(Box::new(move || { - runtest::run(config, &testpaths, revision.as_ref().map(|s| s.as_str())) - })) -} - -/// Returns `true` if the given target is an Android target for the -/// purposes of GDB testing. -fn is_android_gdb_target(target: &String) -> bool { - match &target[..] { - "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => true, - _ => false, - } -} - -/// Returns `true` if the given target is a MSVC target for the purpouses of CDB testing. -fn is_pc_windows_msvc_target(target: &String) -> bool { - target.ends_with("-pc-windows-msvc") -} - -fn find_cdb(target: &String) -> Option { - if !(cfg!(windows) && is_pc_windows_msvc_target(target)) { - return None; - } - - let pf86 = env::var_os("ProgramFiles(x86)").or(env::var_os("ProgramFiles"))?; - let cdb_arch = if cfg!(target_arch="x86") { - "x86" - } else if cfg!(target_arch="x86_64") { - "x64" - } else if cfg!(target_arch="aarch64") { - "arm64" - } else if cfg!(target_arch="arm") { - "arm" - } else { - return None; // No compatible CDB.exe in the Windows 10 SDK - }; - - let mut path = PathBuf::new(); - path.push(pf86); - path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too? - path.push(cdb_arch); - path.push(r"cdb.exe"); - - if !path.exists() { - return None; - } - - Some(path.into_os_string()) -} - -/// Returns Path to CDB -fn analyze_cdb(cdb: Option, target: &String) -> Option { - cdb.map(|s| OsString::from(s)).or(find_cdb(target)) -} - -/// Returns (Path to GDB, GDB Version, GDB has Rust Support) -fn analyze_gdb(gdb: Option, target: &String, android_cross_path: &PathBuf) - -> (Option, Option, bool) { - #[cfg(not(windows))] - const GDB_FALLBACK: &str = "gdb"; - #[cfg(windows)] - const GDB_FALLBACK: &str = "gdb.exe"; - - const MIN_GDB_WITH_RUST: u32 = 7011010; - - let fallback_gdb = || { - if is_android_gdb_target(target) { - let mut gdb_path = match android_cross_path.to_str() { - Some(x) => x.to_owned(), - None => panic!("cannot find android cross path"), - }; - gdb_path.push_str("/bin/gdb"); - gdb_path - } else { - GDB_FALLBACK.to_owned() - } - }; - - let gdb = match gdb { - None => fallback_gdb(), - Some(ref s) if s.is_empty() => fallback_gdb(), // may be empty if configure found no gdb - Some(ref s) => s.to_owned(), - }; - - let mut version_line = None; - if let Ok(output) = Command::new(&gdb).arg("--version").output() { - if let Some(first_line) = String::from_utf8_lossy(&output.stdout).lines().next() { - version_line = Some(first_line.to_string()); - } - } - - let version = match version_line { - Some(line) => extract_gdb_version(&line), - None => return (None, None, false), - }; - - let gdb_native_rust = version.map_or(false, |v| v >= MIN_GDB_WITH_RUST); - - (Some(gdb), version, gdb_native_rust) -} - -fn extract_gdb_version(full_version_line: &str) -> Option { - let full_version_line = full_version_line.trim(); - - // GDB versions look like this: "major.minor.patch?.yyyymmdd?", with both - // of the ? sections being optional - - // We will parse up to 3 digits for minor and patch, ignoring the date - // We limit major to 1 digit, otherwise, on openSUSE, we parse the openSUSE version - - // don't start parsing in the middle of a number - let mut prev_was_digit = false; - for (pos, c) in full_version_line.char_indices() { - if prev_was_digit || !c.is_digit(10) { - prev_was_digit = c.is_digit(10); - continue; - } - - prev_was_digit = true; - - let line = &full_version_line[pos..]; - - let next_split = match line.find(|c: char| !c.is_digit(10)) { - Some(idx) => idx, - None => continue, // no minor version - }; - - if line.as_bytes()[next_split] != b'.' { - continue; // no minor version - } - - let major = &line[..next_split]; - let line = &line[next_split + 1..]; - - let (minor, patch) = match line.find(|c: char| !c.is_digit(10)) { - Some(idx) => if line.as_bytes()[idx] == b'.' { - let patch = &line[idx + 1..]; - - let patch_len = patch - .find(|c: char| !c.is_digit(10)) - .unwrap_or_else(|| patch.len()); - let patch = &patch[..patch_len]; - let patch = if patch_len > 3 || patch_len == 0 { - None - } else { - Some(patch) - }; - - (&line[..idx], patch) - } else { - (&line[..idx], None) - }, - None => (line, None), - }; - - if major.len() != 1 || minor.is_empty() { - continue; - } - - let major: u32 = major.parse().unwrap(); - let minor: u32 = minor.parse().unwrap(); - let patch: u32 = patch.unwrap_or("0").parse().unwrap(); - - return Some(((major * 1000) + minor) * 1000 + patch); - } - - None -} - -/// Returns (LLDB version, LLDB is rust-enabled) -fn extract_lldb_version(full_version_line: Option) -> (Option, bool) { - // Extract the major LLDB version from the given version string. - // LLDB version strings are different for Apple and non-Apple platforms. - // The Apple variant looks like this: - // - // LLDB-179.5 (older versions) - // lldb-300.2.51 (new versions) - // - // We are only interested in the major version number, so this function - // will return `Some("179")` and `Some("300")` respectively. - // - // Upstream versions look like: - // lldb version 6.0.1 - // - // There doesn't seem to be a way to correlate the Apple version - // with the upstream version, and since the tests were originally - // written against Apple versions, we make a fake Apple version by - // multiplying the first number by 100. This is a hack, but - // normally fine because the only non-Apple version we test is - // rust-enabled. - - if let Some(ref full_version_line) = full_version_line { - if !full_version_line.trim().is_empty() { - let full_version_line = full_version_line.trim(); - - for (pos, l) in full_version_line.char_indices() { - if l != 'l' && l != 'L' { - continue; - } - if pos + 5 >= full_version_line.len() { - continue; - } - let l = full_version_line[pos + 1..].chars().next().unwrap(); - if l != 'l' && l != 'L' { - continue; - } - let d = full_version_line[pos + 2..].chars().next().unwrap(); - if d != 'd' && d != 'D' { - continue; - } - let b = full_version_line[pos + 3..].chars().next().unwrap(); - if b != 'b' && b != 'B' { - continue; - } - let dash = full_version_line[pos + 4..].chars().next().unwrap(); - if dash != '-' { - continue; - } - - let vers = full_version_line[pos + 5..] - .chars() - .take_while(|c| c.is_digit(10)) - .collect::(); - if !vers.is_empty() { - return (Some(vers), full_version_line.contains("rust-enabled")); - } - } - - if full_version_line.starts_with("lldb version ") { - let vers = full_version_line[13..] - .chars() - .take_while(|c| c.is_digit(10)) - .collect::(); - if !vers.is_empty() { - return (Some(vers + "00"), full_version_line.contains("rust-enabled")); - } - } - } - } - (None, false) -} - -fn is_blacklisted_lldb_version(version: &str) -> bool { - version == "350" -} diff --git a/src/tools/compiletest/src/raise_fd_limit.rs b/src/tools/compiletest/src/raise_fd_limit.rs deleted file mode 100644 index e9c91094104e9..0000000000000 --- a/src/tools/compiletest/src/raise_fd_limit.rs +++ /dev/null @@ -1,63 +0,0 @@ -/// darwin_fd_limit exists to work around an issue where launchctl on macOS -/// defaults the rlimit maxfiles to 256/unlimited. The default soft limit of 256 -/// ends up being far too low for our multithreaded scheduler testing, depending -/// on the number of cores available. -/// -/// This fixes issue #7772. -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[allow(non_camel_case_types)] -pub unsafe fn raise_fd_limit() { - use std::cmp; - use std::io; - use std::mem::size_of_val; - use std::ptr::null_mut; - - static CTL_KERN: libc::c_int = 1; - static KERN_MAXFILESPERPROC: libc::c_int = 29; - - // The strategy here is to fetch the current resource limits, read the - // kern.maxfilesperproc sysctl value, and bump the soft resource limit for - // maxfiles up to the sysctl value. - - // Fetch the kern.maxfilesperproc value - let mut mib: [libc::c_int; 2] = [CTL_KERN, KERN_MAXFILESPERPROC]; - let mut maxfiles: libc::c_int = 0; - let mut size: libc::size_t = size_of_val(&maxfiles) as libc::size_t; - if libc::sysctl( - &mut mib[0], - 2, - &mut maxfiles as *mut _ as *mut _, - &mut size, - null_mut(), - 0, - ) != 0 - { - let err = io::Error::last_os_error(); - panic!("raise_fd_limit: error calling sysctl: {}", err); - } - - // Fetch the current resource limits - let mut rlim = libc::rlimit { - rlim_cur: 0, - rlim_max: 0, - }; - if libc::getrlimit(libc::RLIMIT_NOFILE, &mut rlim) != 0 { - let err = io::Error::last_os_error(); - panic!("raise_fd_limit: error calling getrlimit: {}", err); - } - - // Make sure we're only ever going to increase the rlimit. - if rlim.rlim_cur < maxfiles as libc::rlim_t { - // Bump the soft limit to the smaller of kern.maxfilesperproc and the hard limit. - rlim.rlim_cur = cmp::min(maxfiles as libc::rlim_t, rlim.rlim_max); - - // Set our newly-increased resource limit. - if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) != 0 { - let err = io::Error::last_os_error(); - panic!("raise_fd_limit: error calling setrlimit: {}", err); - } - } -} - -#[cfg(not(any(target_os = "macos", target_os = "ios")))] -pub unsafe fn raise_fd_limit() {} diff --git a/src/tools/compiletest/src/read2.rs b/src/tools/compiletest/src/read2.rs deleted file mode 100644 index 6dfd8e97c636d..0000000000000 --- a/src/tools/compiletest/src/read2.rs +++ /dev/null @@ -1,205 +0,0 @@ -// FIXME: This is a complete copy of `cargo/src/cargo/util/read2.rs` -// Consider unify the read2() in libstd, cargo and this to prevent further code duplication. - -pub use self::imp::read2; - -#[cfg(not(any(unix, windows)))] -mod imp { - use std::io::{self, Read}; - use std::process::{ChildStderr, ChildStdout}; - - pub fn read2( - out_pipe: ChildStdout, - err_pipe: ChildStderr, - data: &mut dyn FnMut(bool, &mut Vec, bool), - ) -> io::Result<()> { - let mut buffer = Vec::new(); - out_pipe.read_to_end(&mut buffer)?; - data(true, &mut buffer, true); - buffer.clear(); - err_pipe.read_to_end(&mut buffer)?; - data(false, &mut buffer, true); - Ok(()) - } -} - -#[cfg(unix)] -mod imp { - use libc; - use std::io; - use std::io::prelude::*; - use std::mem; - use std::os::unix::prelude::*; - use std::process::{ChildStderr, ChildStdout}; - - pub fn read2( - mut out_pipe: ChildStdout, - mut err_pipe: ChildStderr, - data: &mut dyn FnMut(bool, &mut Vec, bool), - ) -> io::Result<()> { - unsafe { - libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK); - libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK); - } - - let mut out_done = false; - let mut err_done = false; - let mut out = Vec::new(); - let mut err = Vec::new(); - - let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() }; - fds[0].fd = out_pipe.as_raw_fd(); - fds[0].events = libc::POLLIN; - fds[1].fd = err_pipe.as_raw_fd(); - fds[1].events = libc::POLLIN; - let mut nfds = 2; - let mut errfd = 1; - - while nfds > 0 { - // wait for either pipe to become readable using `select` - let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) }; - if r == -1 { - let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue; - } - return Err(err); - } - - // Read as much as we can from each pipe, ignoring EWOULDBLOCK or - // EAGAIN. If we hit EOF, then this will happen because the underlying - // reader will return Ok(0), in which case we'll see `Ok` ourselves. In - // this case we flip the other fd back into blocking mode and read - // whatever's leftover on that file descriptor. - let handle = |res: io::Result<_>| match res { - Ok(_) => Ok(true), - Err(e) => { - if e.kind() == io::ErrorKind::WouldBlock { - Ok(false) - } else { - Err(e) - } - } - }; - if !err_done && fds[errfd].revents != 0 && handle(err_pipe.read_to_end(&mut err))? { - err_done = true; - nfds -= 1; - } - data(false, &mut err, err_done); - if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? { - out_done = true; - fds[0].fd = err_pipe.as_raw_fd(); - errfd = 0; - nfds -= 1; - } - data(true, &mut out, out_done); - } - Ok(()) - } -} - -#[cfg(windows)] -mod imp { - use std::io; - use std::os::windows::prelude::*; - use std::process::{ChildStderr, ChildStdout}; - use std::slice; - - use miow::iocp::{CompletionPort, CompletionStatus}; - use miow::pipe::NamedPipe; - use miow::Overlapped; - use winapi::shared::winerror::ERROR_BROKEN_PIPE; - - struct Pipe<'a> { - dst: &'a mut Vec, - overlapped: Overlapped, - pipe: NamedPipe, - done: bool, - } - - pub fn read2( - out_pipe: ChildStdout, - err_pipe: ChildStderr, - data: &mut dyn FnMut(bool, &mut Vec, bool), - ) -> io::Result<()> { - let mut out = Vec::new(); - let mut err = Vec::new(); - - let port = CompletionPort::new(1)?; - port.add_handle(0, &out_pipe)?; - port.add_handle(1, &err_pipe)?; - - unsafe { - let mut out_pipe = Pipe::new(out_pipe, &mut out); - let mut err_pipe = Pipe::new(err_pipe, &mut err); - - out_pipe.read()?; - err_pipe.read()?; - - let mut status = [CompletionStatus::zero(), CompletionStatus::zero()]; - - while !out_pipe.done || !err_pipe.done { - for status in port.get_many(&mut status, None)? { - if status.token() == 0 { - out_pipe.complete(status); - data(true, out_pipe.dst, out_pipe.done); - out_pipe.read()?; - } else { - err_pipe.complete(status); - data(false, err_pipe.dst, err_pipe.done); - err_pipe.read()?; - } - } - } - - Ok(()) - } - } - - impl<'a> Pipe<'a> { - unsafe fn new(p: P, dst: &'a mut Vec) -> Pipe<'a> { - Pipe { - dst: dst, - pipe: NamedPipe::from_raw_handle(p.into_raw_handle()), - overlapped: Overlapped::zero(), - done: false, - } - } - - unsafe fn read(&mut self) -> io::Result<()> { - let dst = slice_to_end(self.dst); - match self.pipe.read_overlapped(dst, self.overlapped.raw()) { - Ok(_) => Ok(()), - Err(e) => { - if e.raw_os_error() == Some(ERROR_BROKEN_PIPE as i32) { - self.done = true; - Ok(()) - } else { - Err(e) - } - } - } - } - - unsafe fn complete(&mut self, status: &CompletionStatus) { - let prev = self.dst.len(); - self.dst.set_len(prev + status.bytes_transferred() as usize); - if status.bytes_transferred() == 0 { - self.done = true; - } - } - } - - unsafe fn slice_to_end(v: &mut Vec) -> &mut [u8] { - if v.capacity() == 0 { - v.reserve(16); - } - if v.capacity() == v.len() { - v.reserve(1); - } - slice::from_raw_parts_mut( - v.as_mut_ptr().offset(v.len() as isize), - v.capacity() - v.len(), - ) - } -} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs deleted file mode 100644 index 3da6be74129f4..0000000000000 --- a/src/tools/compiletest/src/runtest.rs +++ /dev/null @@ -1,3711 +0,0 @@ -// ignore-tidy-filelength - -use crate::common::{CompareMode, PassMode}; -use crate::common::{expected_output_path, UI_EXTENSIONS, UI_FIXED, UI_STDERR, UI_STDOUT}; -use crate::common::{output_base_dir, output_base_name, output_testname_unique}; -use crate::common::{Codegen, CodegenUnits, Rustdoc}; -use crate::common::{DebugInfoCdb, DebugInfoGdbLldb, DebugInfoGdb, DebugInfoLldb}; -use crate::common::{CompileFail, Pretty, RunFail, RunPassValgrind}; -use crate::common::{Config, TestPaths}; -use crate::common::{Incremental, MirOpt, RunMake, Ui, JsDocTest, Assembly}; -use diff; -use crate::errors::{self, Error, ErrorKind}; -use crate::header::TestProps; -use crate::json; -use regex::{Captures, Regex}; -use rustfix::{apply_suggestions, get_suggestions_from_json, Filter}; -use crate::util::{logv, PathBufExt}; - -use std::collections::hash_map::DefaultHasher; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::env; -use std::ffi::{OsStr, OsString}; -use std::fmt; -use std::fs::{self, create_dir_all, File, OpenOptions}; -use std::hash::{Hash, Hasher}; -use std::io::prelude::*; -use std::io::{self, BufReader}; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command, ExitStatus, Output, Stdio}; -use std::str; - -use lazy_static::lazy_static; -use log::*; - -use crate::extract_gdb_version; -use crate::is_android_gdb_target; - -#[cfg(test)] -mod tests; - -#[cfg(windows)] -fn disable_error_reporting R, R>(f: F) -> R { - use std::sync::Mutex; - const SEM_NOGPFAULTERRORBOX: u32 = 0x0002; - extern "system" { - fn SetErrorMode(mode: u32) -> u32; - } - - lazy_static! { - static ref LOCK: Mutex<()> = { Mutex::new(()) }; - } - // Error mode is a global variable, so lock it so only one thread will change it - let _lock = LOCK.lock().unwrap(); - - // Tell Windows to not show any UI on errors (such as terminating abnormally). - // This is important for running tests, since some of them use abnormal - // termination by design. This mode is inherited by all child processes. - unsafe { - let old_mode = SetErrorMode(SEM_NOGPFAULTERRORBOX); // read inherited flags - SetErrorMode(old_mode | SEM_NOGPFAULTERRORBOX); - let r = f(); - SetErrorMode(old_mode); - r - } -} - -#[cfg(not(windows))] -fn disable_error_reporting R, R>(f: F) -> R { - f() -} - -/// The name of the environment variable that holds dynamic library locations. -pub fn dylib_env_var() -> &'static str { - if cfg!(windows) { - "PATH" - } else if cfg!(target_os = "macos") { - "DYLD_LIBRARY_PATH" - } else if cfg!(target_os = "haiku") { - "LIBRARY_PATH" - } else { - "LD_LIBRARY_PATH" - } -} - -/// The platform-specific library name -pub fn get_lib_name(lib: &str, dylib: bool) -> String { - // In some casess (e.g. MUSL), we build a static - // library, rather than a dynamic library. - // In this case, the only path we can pass - // with '--extern-meta' is the '.lib' file - if !dylib { - return format!("lib{}.rlib", lib); - } - - if cfg!(windows) { - format!("{}.dll", lib) - } else if cfg!(target_os = "macos") { - format!("lib{}.dylib", lib) - } else { - format!("lib{}.so", lib) - } -} - -#[derive(Debug, PartialEq)] -pub enum DiffLine { - Context(String), - Expected(String), - Resulting(String), -} - -#[derive(Debug, PartialEq)] -pub struct Mismatch { - pub line_number: u32, - pub lines: Vec, -} - -impl Mismatch { - fn new(line_number: u32) -> Mismatch { - Mismatch { - line_number: line_number, - lines: Vec::new(), - } - } -} - -// Produces a diff between the expected output and actual output. -pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec { - let mut line_number = 1; - let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size); - let mut lines_since_mismatch = context_size + 1; - let mut results = Vec::new(); - let mut mismatch = Mismatch::new(0); - - for result in diff::lines(expected, actual) { - match result { - diff::Result::Left(str) => { - if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { - results.push(mismatch); - mismatch = Mismatch::new(line_number - context_queue.len() as u32); - } - - while let Some(line) = context_queue.pop_front() { - mismatch.lines.push(DiffLine::Context(line.to_owned())); - } - - mismatch.lines.push(DiffLine::Expected(str.to_owned())); - line_number += 1; - lines_since_mismatch = 0; - } - diff::Result::Right(str) => { - if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { - results.push(mismatch); - mismatch = Mismatch::new(line_number - context_queue.len() as u32); - } - - while let Some(line) = context_queue.pop_front() { - mismatch.lines.push(DiffLine::Context(line.to_owned())); - } - - mismatch.lines.push(DiffLine::Resulting(str.to_owned())); - lines_since_mismatch = 0; - } - diff::Result::Both(str, _) => { - if context_queue.len() >= context_size { - let _ = context_queue.pop_front(); - } - - if lines_since_mismatch < context_size { - mismatch.lines.push(DiffLine::Context(str.to_owned())); - } else if context_size > 0 { - context_queue.push_back(str); - } - - line_number += 1; - lines_since_mismatch += 1; - } - } - } - - results.push(mismatch); - results.remove(0); - - results -} - -pub fn run(config: Config, testpaths: &TestPaths, revision: Option<&str>) { - match &*config.target { - "arm-linux-androideabi" - | "armv7-linux-androideabi" - | "thumbv7neon-linux-androideabi" - | "aarch64-linux-android" => { - if !config.adb_device_status { - panic!("android device not available"); - } - } - - _ => { - // android has its own gdb handling - if config.mode == DebugInfoGdb && config.gdb.is_none() { - panic!("gdb not available but debuginfo gdb debuginfo test requested"); - } - } - } - - if config.verbose { - // We're going to be dumping a lot of info. Start on a new line. - print!("\n\n"); - } - debug!("running {:?}", testpaths.file.display()); - let props = TestProps::from_file(&testpaths.file, revision, &config); - - let cx = TestCx { - config: &config, - props: &props, - testpaths, - revision: revision, - }; - create_dir_all(&cx.output_base_dir()).unwrap(); - - if config.mode == Incremental { - // Incremental tests are special because they cannot be run in - // parallel. - assert!( - !props.revisions.is_empty(), - "Incremental tests require revisions." - ); - cx.init_incremental_test(); - for revision in &props.revisions { - let revision_props = TestProps::from_file(&testpaths.file, Some(revision), &config); - let rev_cx = TestCx { - config: &config, - props: &revision_props, - testpaths, - revision: Some(revision), - }; - rev_cx.run_revision(); - } - } else { - cx.run_revision(); - } - - cx.create_stamp(); -} - -pub fn compute_stamp_hash(config: &Config) -> String { - let mut hash = DefaultHasher::new(); - config.stage_id.hash(&mut hash); - - if config.mode == DebugInfoCdb { - config.cdb.hash(&mut hash); - } - - if config.mode == DebugInfoGdb || config.mode == DebugInfoGdbLldb { - match config.gdb { - None => env::var_os("PATH").hash(&mut hash), - Some(ref s) if s.is_empty() => env::var_os("PATH").hash(&mut hash), - Some(ref s) => s.hash(&mut hash), - }; - } - - if config.mode == DebugInfoLldb || config.mode == DebugInfoGdbLldb { - env::var_os("PATH").hash(&mut hash); - env::var_os("PYTHONPATH").hash(&mut hash); - } - - if let Ui | Incremental | Pretty = config.mode { - config.force_pass_mode.hash(&mut hash); - } - - format!("{:x}", hash.finish()) -} - -struct TestCx<'test> { - config: &'test Config, - props: &'test TestProps, - testpaths: &'test TestPaths, - revision: Option<&'test str>, -} - -struct DebuggerCommands { - commands: Vec, - check_lines: Vec, - breakpoint_lines: Vec, -} - -enum ReadFrom { - Path, - Stdin(String), -} - -impl<'test> TestCx<'test> { - /// Code executed for each revision in turn (or, if there are no - /// revisions, exactly once, with revision == None). - fn run_revision(&self) { - match self.config.mode { - CompileFail => self.run_cfail_test(), - RunFail => self.run_rfail_test(), - RunPassValgrind => self.run_valgrind_test(), - Pretty => self.run_pretty_test(), - DebugInfoGdbLldb => { - self.run_debuginfo_gdb_test(); - self.run_debuginfo_lldb_test(); - }, - DebugInfoCdb => self.run_debuginfo_cdb_test(), - DebugInfoGdb => self.run_debuginfo_gdb_test(), - DebugInfoLldb => self.run_debuginfo_lldb_test(), - Codegen => self.run_codegen_test(), - Rustdoc => self.run_rustdoc_test(), - CodegenUnits => self.run_codegen_units_test(), - Incremental => self.run_incremental_test(), - RunMake => self.run_rmake_test(), - Ui => self.run_ui_test(), - MirOpt => self.run_mir_opt_test(), - Assembly => self.run_assembly_test(), - JsDocTest => self.run_js_doc_test(), - } - } - - fn pass_mode(&self) -> Option { - self.props.pass_mode(self.config) - } - - fn should_run_successfully(&self) -> bool { - let pass_mode = self.pass_mode(); - match self.config.mode { - Ui => pass_mode == Some(PassMode::Run), - mode => panic!("unimplemented for mode {:?}", mode), - } - } - - fn should_compile_successfully(&self) -> bool { - match self.config.mode { - CompileFail => false, - JsDocTest => true, - Ui => self.pass_mode().is_some(), - Incremental => { - let revision = self.revision - .expect("incremental tests require a list of revisions"); - if revision.starts_with("rpass") || revision.starts_with("rfail") { - true - } else if revision.starts_with("cfail") { - // FIXME: would be nice if incremental revs could start with "cpass" - self.pass_mode().is_some() - } else { - panic!("revision name must begin with rpass, rfail, or cfail"); - } - } - mode => panic!("unimplemented for mode {:?}", mode), - } - } - - fn check_if_test_should_compile(&self, proc_res: &ProcRes) { - if self.should_compile_successfully() { - if !proc_res.status.success() { - self.fatal_proc_rec("test compilation failed although it shouldn't!", proc_res); - } - } else { - if proc_res.status.success() { - self.fatal_proc_rec( - &format!("{} test compiled successfully!", self.config.mode)[..], - proc_res, - ); - } - - self.check_correct_failure_status(proc_res); - } - } - - fn run_cfail_test(&self) { - let proc_res = self.compile_test(); - self.check_if_test_should_compile(&proc_res); - self.check_no_compiler_crash(&proc_res); - - let output_to_check = self.get_output(&proc_res); - let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); - if !expected_errors.is_empty() { - if !self.props.error_patterns.is_empty() { - self.fatal("both error pattern and expected errors specified"); - } - self.check_expected_errors(expected_errors, &proc_res); - } else { - self.check_error_patterns(&output_to_check, &proc_res); - } - - self.check_forbid_output(&output_to_check, &proc_res); - } - - fn run_rfail_test(&self) { - let proc_res = self.compile_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - let proc_res = self.exec_compiled_test(); - - // The value our Makefile configures valgrind to return on failure - const VALGRIND_ERR: i32 = 100; - if proc_res.status.code() == Some(VALGRIND_ERR) { - self.fatal_proc_rec("run-fail test isn't valgrind-clean!", &proc_res); - } - - let output_to_check = self.get_output(&proc_res); - self.check_correct_failure_status(&proc_res); - self.check_error_patterns(&output_to_check, &proc_res); - } - - fn get_output(&self, proc_res: &ProcRes) -> String { - if self.props.check_stdout { - format!("{}{}", proc_res.stdout, proc_res.stderr) - } else { - proc_res.stderr.clone() - } - } - - fn check_correct_failure_status(&self, proc_res: &ProcRes) { - let expected_status = Some(self.props.failure_status); - let received_status = proc_res.status.code(); - - if expected_status != received_status { - self.fatal_proc_rec( - &format!( - "Error: expected failure status ({:?}) but received status {:?}.", - expected_status, received_status - ), - proc_res, - ); - } - } - - fn run_rpass_test(&self) { - let proc_res = self.compile_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - // FIXME(#41968): Move this check to tidy? - let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); - assert!( - expected_errors.is_empty(), - "run-pass tests with expected warnings should be moved to ui/" - ); - - let proc_res = self.exec_compiled_test(); - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } - - fn run_valgrind_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - if self.config.valgrind_path.is_none() { - assert!(!self.config.force_valgrind); - return self.run_rpass_test(); - } - - let mut proc_res = self.compile_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - let mut new_config = self.config.clone(); - new_config.runtool = new_config.valgrind_path.clone(); - let new_cx = TestCx { - config: &new_config, - ..*self - }; - proc_res = new_cx.exec_compiled_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } - - fn run_pretty_test(&self) { - if self.props.pp_exact.is_some() { - logv(self.config, "testing for exact pretty-printing".to_owned()); - } else { - logv( - self.config, - "testing for converging pretty-printing".to_owned(), - ); - } - - let rounds = match self.props.pp_exact { - Some(_) => 1, - None => 2, - }; - - let src = fs::read_to_string(&self.testpaths.file).unwrap(); - let mut srcs = vec![src]; - - let mut round = 0; - while round < rounds { - logv( - self.config, - format!( - "pretty-printing round {} revision {:?}", - round, self.revision - ), - ); - let read_from = if round == 0 { - ReadFrom::Path - } else { - ReadFrom::Stdin(srcs[round].to_owned()) - }; - - let proc_res = self.print_source(read_from, - &self.props.pretty_mode); - if !proc_res.status.success() { - self.fatal_proc_rec( - &format!( - "pretty-printing failed in round {} revision {:?}", - round, self.revision - ), - &proc_res, - ); - } - - let ProcRes { stdout, .. } = proc_res; - srcs.push(stdout); - round += 1; - } - - let mut expected = match self.props.pp_exact { - Some(ref file) => { - let filepath = self.testpaths.file.parent().unwrap().join(file); - fs::read_to_string(&filepath).unwrap() - } - None => srcs[srcs.len() - 2].clone(), - }; - let mut actual = srcs[srcs.len() - 1].clone(); - - if self.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(); - } - - self.compare_source(&expected, &actual); - - // If we're only making sure that the output matches then just stop here - if self.props.pretty_compare_only { - return; - } - - // Finally, let's make sure it actually appears to remain valid code - let proc_res = self.typecheck_source(actual); - if !proc_res.status.success() { - self.fatal_proc_rec("pretty-printed source does not typecheck", &proc_res); - } - - if !self.props.pretty_expanded { - return; - } - - // additionally, run `--pretty expanded` and try to build it. - let proc_res = self.print_source(ReadFrom::Path, "expanded"); - if !proc_res.status.success() { - self.fatal_proc_rec("pretty-printing (expanded) failed", &proc_res); - } - - let ProcRes { - stdout: expanded_src, - .. - } = proc_res; - let proc_res = self.typecheck_source(expanded_src); - if !proc_res.status.success() { - self.fatal_proc_rec( - "pretty-printed source (expanded) does not typecheck", - &proc_res, - ); - } - } - - fn print_source(&self, read_from: ReadFrom, pretty_type: &str) -> ProcRes { - let aux_dir = self.aux_output_dir_name(); - let input: &str = match read_from { - ReadFrom::Stdin(_) => "-", - ReadFrom::Path => self.testpaths.file.to_str().unwrap(), - }; - - let mut rustc = Command::new(&self.config.rustc_path); - rustc - .arg(input) - .args(&["-Z", &format!("unpretty={}", pretty_type)]) - .args(&["--target", &self.config.target]) - .arg("-L") - .arg(&aux_dir) - .args(&self.props.compile_flags) - .envs(self.props.exec_env.clone()); - self.maybe_add_external_args(&mut rustc, - self.split_maybe_args(&self.config.target_rustcflags)); - - let src = match read_from { - ReadFrom::Stdin(src) => Some(src), - ReadFrom::Path => None - }; - - self.compose_and_run( - rustc, - self.config.compile_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - src, - ) - } - - fn compare_source(&self, expected: &str, actual: &str) { - if expected != actual { - self.fatal(&format!( - "pretty-printed source does not match expected source\n\ - expected:\n\ - ------------------------------------------\n\ - {}\n\ - ------------------------------------------\n\ - actual:\n\ - ------------------------------------------\n\ - {}\n\ - ------------------------------------------\n\ - \n", - expected, actual) - ); - } - } - - fn set_revision_flags(&self, cmd: &mut Command) { - if let Some(revision) = self.revision { - // Normalize revisions to be lowercase and replace `-`s with `_`s. - // Otherwise the `--cfg` flag is not valid. - let normalized_revision = revision.to_lowercase().replace("-", "_"); - cmd.args(&["--cfg", &normalized_revision]); - } - } - - fn typecheck_source(&self, src: String) -> ProcRes { - let mut rustc = Command::new(&self.config.rustc_path); - - let out_dir = self.output_base_name().with_extension("pretty-out"); - let _ = fs::remove_dir_all(&out_dir); - create_dir_all(&out_dir).unwrap(); - - let target = if self.props.force_host { - &*self.config.host - } else { - &*self.config.target - }; - - let aux_dir = self.aux_output_dir_name(); - - rustc - .arg("-") - .arg("-Zno-codegen") - .arg("--out-dir") - .arg(&out_dir) - .arg(&format!("--target={}", target)) - .arg("-L") - .arg(&self.config.build_base) - .arg("-L") - .arg(aux_dir); - self.set_revision_flags(&mut rustc); - self.maybe_add_external_args(&mut rustc, - self.split_maybe_args(&self.config.target_rustcflags)); - rustc.args(&self.props.compile_flags); - - self.compose_and_run_compiler(rustc, Some(src)) - } - - fn run_debuginfo_cdb_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - let config = Config { - target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), - host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), - mode: DebugInfoCdb, - ..self.config.clone() - }; - - let test_cx = TestCx { - config: &config, - ..*self - }; - - test_cx.run_debuginfo_cdb_test_no_opt(); - } - - fn run_debuginfo_cdb_test_no_opt(&self) { - // compile test file (it should have 'compile-flags:-g' in the header) - let compile_result = self.compile_test(); - if !compile_result.status.success() { - self.fatal_proc_rec("compilation failed!", &compile_result); - } - - let exe_file = self.make_exe_name(); - - let prefixes = { - static PREFIXES: &'static [&'static str] = &["cdb", "cdbg"]; - // No "native rust support" variation for CDB yet. - PREFIXES - }; - - // Parse debugger commands etc from test files - let DebuggerCommands { - commands, - check_lines, - breakpoint_lines, - .. - } = self.parse_debugger_commands(prefixes); - - // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-commands - let mut script_str = String::with_capacity(2048); - script_str.push_str("version\n"); // List CDB (and more) version info in test output - script_str.push_str(".nvlist\n"); // List loaded `*.natvis` files, bulk of custom MSVC debug - - // Set breakpoints on every line that contains the string "#break" - let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); - for line in &breakpoint_lines { - script_str.push_str(&format!( - "bp `{}:{}`\n", - source_file_name, line - )); - } - - // Append the other `cdb-command:`s - for line in &commands { - script_str.push_str(line); - script_str.push_str("\n"); - } - - script_str.push_str("\nqq\n"); // Quit the debugger (including remote debugger, if any) - - // Write the script into a file - debug!("script_str = {}", script_str); - self.dump_output_file(&script_str, "debugger.script"); - let debugger_script = self.make_out_name("debugger.script"); - - let cdb_path = &self.config.cdb.as_ref().unwrap(); - let mut cdb = Command::new(cdb_path); - cdb - .arg("-lines") // Enable source line debugging. - .arg("-cf").arg(&debugger_script) - .arg(&exe_file); - - let debugger_run_result = self.compose_and_run( - cdb, - self.config.run_lib_path.to_str().unwrap(), - None, // aux_path - None // input - ); - - if !debugger_run_result.status.success() { - self.fatal_proc_rec("Error while running CDB", &debugger_run_result); - } - - self.check_debugger_output(&debugger_run_result, &check_lines); - } - - fn run_debuginfo_gdb_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - let config = Config { - target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), - host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), - mode: DebugInfoGdb, - ..self.config.clone() - }; - - let test_cx = TestCx { - config: &config, - ..*self - }; - - test_cx.run_debuginfo_gdb_test_no_opt(); - } - - fn run_debuginfo_gdb_test_no_opt(&self) { - let prefixes = if self.config.gdb_native_rust { - // GDB with Rust - static PREFIXES: &'static [&'static str] = &["gdb", "gdbr"]; - println!("NOTE: compiletest thinks it is using GDB with native rust support"); - PREFIXES - } else { - // Generic GDB - static PREFIXES: &'static [&'static str] = &["gdb", "gdbg"]; - println!("NOTE: compiletest thinks it is using GDB without native rust support"); - PREFIXES - }; - - let DebuggerCommands { - commands, - check_lines, - breakpoint_lines, - } = self.parse_debugger_commands(prefixes); - let mut cmds = commands.join("\n"); - - // compile test file (it should have 'compile-flags:-g' in the header) - let compiler_run_result = self.compile_test(); - if !compiler_run_result.status.success() { - self.fatal_proc_rec("compilation failed!", &compiler_run_result); - } - - let exe_file = self.make_exe_name(); - - let debugger_run_result; - if is_android_gdb_target(&self.config.target) { - cmds = cmds.replace("run", "continue"); - - let tool_path = match self.config.android_cross_path.to_str() { - Some(x) => x.to_owned(), - None => self.fatal("cannot find android cross path"), - }; - - // write debugger script - let mut script_str = String::with_capacity(2048); - script_str.push_str(&format!("set charset {}\n", Self::charset())); - script_str.push_str(&format!("set sysroot {}\n", tool_path)); - 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", - self.config.host, self.config.target - )); - for line in &breakpoint_lines { - script_str.push_str( - &format!( - "break {:?}:{}\n", - self.testpaths.file.file_name().unwrap().to_string_lossy(), - *line - )[..], - ); - } - script_str.push_str(&cmds); - script_str.push_str("\nquit\n"); - - debug!("script_str = {}", script_str); - self.dump_output_file(&script_str, "debugger.script"); - - let adb_path = &self.config.adb_path; - - Command::new(adb_path) - .arg("push") - .arg(&exe_file) - .arg(&self.config.adb_test_dir) - .status() - .expect(&format!("failed to exec `{:?}`", adb_path)); - - Command::new(adb_path) - .args(&["forward", "tcp:5039", "tcp:5039"]) - .status() - .expect(&format!("failed to exec `{:?}`", adb_path)); - - let adb_arg = format!( - "export LD_LIBRARY_PATH={}; \ - gdbserver{} :5039 {}/{}", - self.config.adb_test_dir.clone(), - if self.config.target.contains("aarch64") { - "64" - } else { - "" - }, - self.config.adb_test_dir.clone(), - exe_file.file_name().unwrap().to_str().unwrap() - ); - - debug!("adb arg: {}", adb_arg); - let mut adb = Command::new(adb_path) - .args(&["shell", &adb_arg]) - .stdout(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn() - .expect(&format!("failed to exec `{:?}`", adb_path)); - - // Wait for the gdbserver to print out "Listening on port ..." - // at which point we know that it's started and then we can - // execute the debugger below. - let mut stdout = BufReader::new(adb.stdout.take().unwrap()); - let mut line = String::new(); - loop { - line.truncate(0); - stdout.read_line(&mut line).unwrap(); - if line.starts_with("Listening on port 5039") { - break; - } - } - drop(stdout); - - let mut debugger_script = OsString::from("-command="); - debugger_script.push(self.make_out_name("debugger.script")); - let debugger_opts: &[&OsStr] = &[ - "-quiet".as_ref(), - "-batch".as_ref(), - "-nx".as_ref(), - &debugger_script, - ]; - - let gdb_path = self.config.gdb.as_ref().unwrap(); - let Output { - status, - stdout, - stderr, - } = Command::new(&gdb_path) - .args(debugger_opts) - .output() - .expect(&format!("failed to exec `{:?}`", gdb_path)); - let cmdline = { - let mut gdb = Command::new(&format!("{}-gdb", self.config.target)); - gdb.args(debugger_opts); - let cmdline = self.make_cmdline(&gdb, ""); - logv(self.config, format!("executing {}", cmdline)); - cmdline - }; - - debugger_run_result = ProcRes { - status, - stdout: String::from_utf8(stdout).unwrap(), - stderr: String::from_utf8(stderr).unwrap(), - cmdline, - }; - if adb.kill().is_err() { - println!("Adb process is already finished."); - } - } else { - let rust_src_root = self - .config - .find_rust_src_root() - .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", Self::charset())); - script_str.push_str("show version\n"); - - match self.config.gdb_version { - Some(version) => { - println!( - "NOTE: compiletest thinks it is using GDB version {}", - version - ); - - if version > extract_gdb_version("7.4").unwrap() { - // 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"\\") - )); - - // Force GDB to print values in the Rust format. - if self.config.gdb_native_rust { - script_str.push_str("set language rust\n"); - } - - // Add line breakpoints - for line in &breakpoint_lines { - script_str.push_str(&format!( - "break '{}':{}\n", - self.testpaths.file.file_name().unwrap().to_string_lossy(), - *line - )); - } - - script_str.push_str(&cmds); - script_str.push_str("\nquit\n"); - - debug!("script_str = {}", script_str); - self.dump_output_file(&script_str, "debugger.script"); - - let mut debugger_script = OsString::from("-command="); - debugger_script.push(self.make_out_name("debugger.script")); - - let debugger_opts: &[&OsStr] = &[ - "-quiet".as_ref(), - "-batch".as_ref(), - "-nx".as_ref(), - &debugger_script, - ]; - - let mut gdb = Command::new(self.config.gdb.as_ref().unwrap()); - gdb.args(debugger_opts) - .env("PYTHONPATH", rust_pp_module_abs_path); - - debugger_run_result = self.compose_and_run( - gdb, - self.config.run_lib_path.to_str().unwrap(), - None, - None, - ); - } - - if !debugger_run_result.status.success() { - self.fatal_proc_rec("gdb failed to execute", &debugger_run_result); - } - - self.check_debugger_output(&debugger_run_result, &check_lines); - } - - fn run_debuginfo_lldb_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - if self.config.lldb_python_dir.is_none() { - self.fatal("Can't run LLDB test because LLDB's python path is not set."); - } - - let config = Config { - target_rustcflags: self.cleanup_debug_info_options(&self.config.target_rustcflags), - host_rustcflags: self.cleanup_debug_info_options(&self.config.host_rustcflags), - mode: DebugInfoLldb, - ..self.config.clone() - }; - - let test_cx = TestCx { - config: &config, - ..*self - }; - - test_cx.run_debuginfo_lldb_test_no_opt(); - } - - fn run_debuginfo_lldb_test_no_opt(&self) { - // compile test file (it should have 'compile-flags:-g' in the header) - let compile_result = self.compile_test(); - if !compile_result.status.success() { - self.fatal_proc_rec("compilation failed!", &compile_result); - } - - let exe_file = self.make_exe_name(); - - match self.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" - ); - } - } - - let prefixes = if self.config.lldb_native_rust { - static PREFIXES: &'static [&'static str] = &["lldb", "lldbr"]; - println!("NOTE: compiletest thinks it is using LLDB with native rust support"); - PREFIXES - } else { - static PREFIXES: &'static [&'static str] = &["lldb", "lldbg"]; - println!("NOTE: compiletest thinks it is using LLDB without native rust support"); - PREFIXES - }; - - // Parse debugger commands etc from test files - let DebuggerCommands { - commands, - check_lines, - breakpoint_lines, - .. - } = self.parse_debugger_commands(prefixes); - - // 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 = self - .config - .find_rust_src_root() - .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" - let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy(); - for line in &breakpoint_lines { - script_str.push_str(&format!( - "breakpoint set --file '{}' --line {}\n", - source_file_name, 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); - self.dump_output_file(&script_str, "debugger.script"); - let debugger_script = self.make_out_name("debugger.script"); - - // Let LLDB execute the script via lldb_batchmode.py - let debugger_run_result = self.run_lldb(&exe_file, &debugger_script, &rust_src_root); - - if !debugger_run_result.status.success() { - self.fatal_proc_rec("Error while running LLDB", &debugger_run_result); - } - - self.check_debugger_output(&debugger_run_result, &check_lines); - } - - fn run_lldb( - &self, - 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"); - self.cmd2procres( - Command::new(&self.config.lldb_python) - .arg(&lldb_script_path) - .arg(test_executable) - .arg(debugger_script) - .env("PYTHONPATH", self.config.lldb_python_dir.as_ref().unwrap()), - ) - } - - fn cmd2procres(&self, 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) => self.fatal(&format!( - "Failed to setup Python process for \ - LLDB script: {}", - e - )), - }; - - self.dump_output(&out, &err); - ProcRes { - status, - stdout: out, - stderr: err, - cmdline: format!("{:?}", cmd), - } - } - - fn parse_debugger_commands(&self, debugger_prefixes: &[&str]) -> DebuggerCommands { - let directives = debugger_prefixes - .iter() - .map(|prefix| (format!("{}-command", prefix), format!("{}-check", prefix))) - .collect::>(); - - let mut breakpoint_lines = vec![]; - let mut commands = vec![]; - let mut check_lines = vec![]; - let mut counter = 1; - let reader = BufReader::new(File::open(&self.testpaths.file).unwrap()); - for line in reader.lines() { - match line { - Ok(line) => { - let line = if line.starts_with("//") { - line[2..].trim_start() - } else { - line.as_str() - }; - - if line.contains("#break") { - breakpoint_lines.push(counter); - } - - for &(ref command_directive, ref check_directive) in &directives { - self.config - .parse_name_value_directive(&line, command_directive) - .map(|cmd| commands.push(cmd)); - - self.config - .parse_name_value_directive(&line, check_directive) - .map(|cmd| check_lines.push(cmd)); - } - } - Err(e) => self.fatal(&format!("Error while parsing debugger commands: {}", e)), - } - counter += 1; - } - - DebuggerCommands { - commands, - check_lines, - breakpoint_lines, - } - } - - fn cleanup_debug_info_options(&self, 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 = self - .split_maybe_args(options) - .into_iter() - .filter(|x| !options_to_remove.contains(x)) - .collect::>(); - - Some(new_options.join(" ")) - } - - fn maybe_add_external_args(&self, cmd: &mut Command, args: Vec) { - // Filter out the arguments that should not be added by runtest here. - // - // Notable use-cases are: do not add our optimisation flag if - // `compile-flags: -Copt-level=x` and similar for debug-info level as well. - const OPT_FLAGS: &[&str] = &["-O", "-Copt-level=", /*-C*/"opt-level="]; - const DEBUG_FLAGS: &[&str] = &["-g", "-Cdebuginfo=", /*-C*/"debuginfo="]; - - // FIXME: ideally we would "just" check the `cmd` itself, but it does not allow inspecting - // its arguments. They need to be collected separately. For now I cannot be bothered to - // implement this the "right" way. - let have_opt_flag = self.props.compile_flags.iter().any(|arg| { - OPT_FLAGS.iter().any(|f| arg.starts_with(f)) - }); - let have_debug_flag = self.props.compile_flags.iter().any(|arg| { - DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) - }); - - for arg in args { - if OPT_FLAGS.iter().any(|f| arg.starts_with(f)) && have_opt_flag { - continue; - } - if DEBUG_FLAGS.iter().any(|f| arg.starts_with(f)) && have_debug_flag { - continue; - } - cmd.arg(arg); - } - } - - fn check_debugger_output(&self, debugger_run_result: &ProcRes, check_lines: &[String]) { - let num_check_lines = check_lines.len(); - - let mut check_line_index = 0; - for line in debugger_run_result.stdout.lines() { - if check_line_index >= num_check_lines { - break; - } - - if check_single_line(line, &(check_lines[check_line_index])[..]) { - check_line_index += 1; - } - } - if check_line_index != num_check_lines && num_check_lines > 0 { - self.fatal_proc_rec( - &format!( - "line not found in debugger output: {}", - check_lines[check_line_index] - ), - debugger_run_result, - ); - } - - fn check_single_line(line: &str, check_line: &str) -> bool { - // Allow check lines to leave parts unspecified (e.g., uninitialized - // bits in the wrong case of an enum) with the notation "[...]". - let line = line.trim(); - let check_line = check_line.trim(); - let can_start_anywhere = check_line.starts_with("[...]"); - let can_end_anywhere = check_line.ends_with("[...]"); - - let check_fragments: Vec<&str> = check_line - .split("[...]") - .filter(|frag| !frag.is_empty()) - .collect(); - if check_fragments.is_empty() { - return true; - } - - let (mut rest, first_fragment) = if can_start_anywhere { - match line.find(check_fragments[0]) { - Some(pos) => (&line[pos + check_fragments[0].len()..], 1), - None => return false, - } - } else { - (line, 0) - }; - - for current_fragment in &check_fragments[first_fragment..] { - match rest.find(current_fragment) { - Some(pos) => { - rest = &rest[pos + current_fragment.len()..]; - } - None => return false, - } - } - - if !can_end_anywhere && !rest.is_empty() { - return false; - } - - true - } - } - - fn check_error_patterns(&self, output_to_check: &str, proc_res: &ProcRes) { - debug!("check_error_patterns"); - if self.props.error_patterns.is_empty() { - if self.pass_mode().is_some() { - return; - } else { - self.fatal(&format!( - "no error pattern specified in {:?}", - self.testpaths.file.display() - )); - } - } - - let mut missing_patterns: Vec = Vec::new(); - - for pattern in &self.props.error_patterns { - if output_to_check.contains(pattern.trim()) { - debug!("found error pattern {}", pattern); - } else { - missing_patterns.push(pattern.to_string()); - } - } - - if missing_patterns.is_empty() { - return; - } - - if missing_patterns.len() == 1 { - self.fatal_proc_rec( - &format!("error pattern '{}' not found!", missing_patterns[0]), - proc_res, - ); - } else { - for pattern in missing_patterns { - self.error(&format!("error pattern '{}' not found!", pattern)); - } - self.fatal_proc_rec("multiple error patterns not found", proc_res); - } - } - - fn check_no_compiler_crash(&self, proc_res: &ProcRes) { - match proc_res.status.code() { - Some(101) => self.fatal_proc_rec("compiler encountered internal error", proc_res), - None => self.fatal_proc_rec("compiler terminated by signal", proc_res), - _ => (), - } - } - - fn check_forbid_output(&self, output_to_check: &str, proc_res: &ProcRes) { - for pat in &self.props.forbid_output { - if output_to_check.contains(pat) { - self.fatal_proc_rec("forbidden pattern found in compiler output", proc_res); - } - } - } - - fn check_expected_errors(&self, expected_errors: Vec, proc_res: &ProcRes) { - debug!("check_expected_errors: expected_errors={:?} proc_res.status={:?}", - expected_errors, proc_res.status); - if proc_res.status.success() - && expected_errors - .iter() - .any(|x| x.kind == Some(ErrorKind::Error)) - { - self.fatal_proc_rec("process did not return an error status", proc_res); - } - - // On Windows, keep all '\' path separators to match the paths reported in the JSON output - // from the compiler - let os_file_name = self.testpaths.file.display().to_string(); - - // on windows, translate all '\' path separators to '/' - let file_name = format!("{}", self.testpaths.file.display()).replace(r"\", "/"); - - // If the testcase being checked contains at least one expected "help" - // message, then we'll ensure that all "help" messages are expected. - // Otherwise, all "help" messages reported by the compiler will be ignored. - // This logic also applies to "note" messages. - let expect_help = expected_errors - .iter() - .any(|ee| ee.kind == Some(ErrorKind::Help)); - let expect_note = expected_errors - .iter() - .any(|ee| ee.kind == Some(ErrorKind::Note)); - - // Parse the JSON output from the compiler and extract out the messages. - let actual_errors = json::parse_output(&os_file_name, &proc_res.stderr, proc_res); - let mut unexpected = Vec::new(); - let mut found = vec![false; expected_errors.len()]; - for actual_error in &actual_errors { - let opt_index = expected_errors.iter().enumerate().position( - |(index, expected_error)| { - !found[index] && actual_error.line_num == expected_error.line_num - && (expected_error.kind.is_none() - || actual_error.kind == expected_error.kind) - && actual_error.msg.contains(&expected_error.msg) - }, - ); - - match opt_index { - Some(index) => { - // found a match, everybody is happy - assert!(!found[index]); - found[index] = true; - } - - None => { - if self.is_unexpected_compiler_message(actual_error, expect_help, expect_note) { - self.error(&format!( - "{}:{}: unexpected {}: '{}'", - file_name, - actual_error.line_num, - actual_error - .kind - .as_ref() - .map_or(String::from("message"), |k| k.to_string()), - actual_error.msg - )); - unexpected.push(actual_error); - } - } - } - } - - let mut not_found = Vec::new(); - // anything not yet found is a problem - for (index, expected_error) in expected_errors.iter().enumerate() { - if !found[index] { - self.error(&format!( - "{}:{}: expected {} not found: {}", - file_name, - expected_error.line_num, - expected_error - .kind - .as_ref() - .map_or("message".into(), |k| k.to_string()), - expected_error.msg - )); - not_found.push(expected_error); - } - } - - if !unexpected.is_empty() || !not_found.is_empty() { - self.error(&format!( - "{} unexpected errors found, {} expected errors not found", - unexpected.len(), - not_found.len() - )); - println!("status: {}\ncommand: {}", proc_res.status, proc_res.cmdline); - if !unexpected.is_empty() { - println!("unexpected errors (from JSON output): {:#?}\n", unexpected); - } - if !not_found.is_empty() { - println!("not found errors (from test file): {:#?}\n", not_found); - } - panic!(); - } - } - - /// Returns `true` if we should report an error about `actual_error`, - /// which did not match any of the expected error. We always require - /// errors/warnings to be explicitly listed, but only require - /// helps/notes if there are explicit helps/notes given. - fn is_unexpected_compiler_message( - &self, - actual_error: &Error, - expect_help: bool, - expect_note: bool, - ) -> bool { - match actual_error.kind { - Some(ErrorKind::Help) => expect_help, - Some(ErrorKind::Note) => expect_note, - Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true, - Some(ErrorKind::Suggestion) | None => false, - } - } - - fn compile_test(&self) -> ProcRes { - // Only use `make_exe_name` when the test ends up being executed. - let will_execute = match self.config.mode { - Ui => self.should_run_successfully(), - Incremental => self.revision.unwrap().starts_with("r"), - RunFail | RunPassValgrind | MirOpt | - DebugInfoCdb | DebugInfoGdbLldb | DebugInfoGdb | DebugInfoLldb => true, - _ => false, - }; - let output_file = if will_execute { - TargetLocation::ThisFile(self.make_exe_name()) - } else { - TargetLocation::ThisDirectory(self.output_base_dir()) - }; - - let mut rustc = self.make_compile_args(&self.testpaths.file, output_file); - - rustc.arg("-L").arg(&self.aux_output_dir_name()); - - match self.config.mode { - CompileFail | Ui => { - // compile-fail and ui tests tend to have tons of unused code as - // it's just testing various pieces of the compile, but we don't - // want to actually assert warnings about all this code. Instead - // let's just ignore unused code warnings by defaults and tests - // can turn it back on if needed. - if !self.config.src_base.ends_with("rustdoc-ui") { - rustc.args(&["-A", "unused"]); - } - } - _ => {} - } - - self.compose_and_run_compiler(rustc, None) - } - - fn document(&self, out_dir: &Path) -> ProcRes { - if self.props.build_aux_docs { - for rel_ab in &self.props.aux_builds { - let aux_testpaths = self.compute_aux_test_paths(rel_ab); - let aux_props = - self.props - .from_aux_file(&aux_testpaths.file, self.revision, self.config); - let aux_cx = TestCx { - config: self.config, - props: &aux_props, - testpaths: &aux_testpaths, - revision: self.revision, - }; - // Create the directory for the stdout/stderr files. - create_dir_all(aux_cx.output_base_dir()).unwrap(); - let auxres = aux_cx.document(out_dir); - if !auxres.status.success() { - return auxres; - } - } - } - - let aux_dir = self.aux_output_dir_name(); - - let rustdoc_path = self - .config - .rustdoc_path - .as_ref() - .expect("--rustdoc-path passed"); - let mut rustdoc = Command::new(rustdoc_path); - - rustdoc - .arg("-L") - .arg(self.config.run_lib_path.to_str().unwrap()) - .arg("-L") - .arg(aux_dir) - .arg("-o") - .arg(out_dir) - .arg(&self.testpaths.file) - .args(&self.props.compile_flags); - - if let Some(ref linker) = self.config.linker { - rustdoc - .arg("--linker") - .arg(linker) - .arg("-Z") - .arg("unstable-options"); - } - - self.compose_and_run_compiler(rustdoc, None) - } - - fn exec_compiled_test(&self) -> ProcRes { - let env = &self.props.exec_env; - - let proc_res = match &*self.config.target { - // This is pretty similar to below, we're transforming: - // - // program arg1 arg2 - // - // into - // - // remote-test-client run program:support-lib.so arg1 arg2 - // - // The test-client program will upload `program` to the emulator - // along with all other support libraries listed (in this case - // `support-lib.so`. It will then execute the program on the - // emulator with the arguments specified (in the environment we give - // the process) and then report back the same result. - _ if self.config.remote_test_client.is_some() => { - let aux_dir = self.aux_output_dir_name(); - let ProcArgs { mut prog, args } = self.make_run_args(); - if let Ok(entries) = aux_dir.read_dir() { - for entry in entries { - let entry = entry.unwrap(); - if !entry.path().is_file() { - continue; - } - prog.push_str(":"); - prog.push_str(entry.path().to_str().unwrap()); - } - } - let mut test_client = - Command::new(self.config.remote_test_client.as_ref().unwrap()); - test_client - .args(&["run", &prog]) - .args(args) - .envs(env.clone()); - self.compose_and_run( - test_client, - self.config.run_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - None, - ) - } - _ if self.config.target.contains("vxworks") => { - let aux_dir = self.aux_output_dir_name(); - let ProcArgs { prog, args } = self.make_run_args(); - let mut wr_run = Command::new("wr-run"); - wr_run.args(&[&prog]).args(args).envs(env.clone()); - self.compose_and_run( - wr_run, - self.config.run_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - None, - ) - } - _ => { - let aux_dir = self.aux_output_dir_name(); - let ProcArgs { prog, args } = self.make_run_args(); - let mut program = Command::new(&prog); - program - .args(args) - .current_dir(&self.output_base_dir()) - .envs(env.clone()); - self.compose_and_run( - program, - self.config.run_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - None, - ) - } - }; - - if proc_res.status.success() { - // delete the executable after running it to save space. - // it is ok if the deletion failed. - let _ = fs::remove_file(self.make_exe_name()); - } - - proc_res - } - - /// For each `aux-build: foo/bar` annotation, we check to find the - /// file in a `auxiliary` directory relative to the test itself. - fn compute_aux_test_paths(&self, rel_ab: &str) -> TestPaths { - let test_ab = self - .testpaths - .file - .parent() - .expect("test file path has no parent") - .join("auxiliary") - .join(rel_ab); - if !test_ab.exists() { - self.fatal(&format!( - "aux-build `{}` source not found", - test_ab.display() - )) - } - - TestPaths { - file: test_ab, - relative_dir: self - .testpaths - .relative_dir - .join(self.output_testname_unique()) - .join("auxiliary") - .join(rel_ab) - .parent() - .expect("aux-build path has no parent") - .to_path_buf(), - } - } - - fn compose_and_run_compiler(&self, mut rustc: Command, input: Option) -> ProcRes { - let aux_dir = self.aux_output_dir_name(); - - if !self.props.aux_builds.is_empty() { - let _ = fs::remove_dir_all(&aux_dir); - create_dir_all(&aux_dir).unwrap(); - } - - // Use a Vec instead of a HashMap to preserve original order - let mut extern_priv = self.props.extern_private.clone(); - - let mut add_extern_priv = |priv_dep: &str, dylib: bool| { - let lib_name = get_lib_name(priv_dep, dylib); - rustc - .arg("--extern-private") - .arg(format!("{}={}", priv_dep, aux_dir.join(lib_name).to_str().unwrap())); - }; - - for rel_ab in &self.props.aux_builds { - let aux_testpaths = self.compute_aux_test_paths(rel_ab); - let aux_props = - self.props - .from_aux_file(&aux_testpaths.file, self.revision, self.config); - let aux_output = TargetLocation::ThisDirectory(self.aux_output_dir_name()); - let aux_cx = TestCx { - config: self.config, - props: &aux_props, - testpaths: &aux_testpaths, - revision: self.revision, - }; - // Create the directory for the stdout/stderr files. - create_dir_all(aux_cx.output_base_dir()).unwrap(); - let mut aux_rustc = aux_cx.make_compile_args(&aux_testpaths.file, aux_output); - - let (dylib, crate_type) = if aux_props.no_prefer_dynamic { - (true, None) - } else if self.config.target.contains("cloudabi") - || self.config.target.contains("emscripten") - || (self.config.target.contains("musl") - && !aux_props.force_host - && !self.config.host.contains("musl")) - || self.config.target.contains("wasm32") - || self.config.target.contains("nvptx") - { - // 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 or Emscripten, however, there is no support for - // dynamic libraries so we just go back to building a normal library. Note, - // however, that for MUSL if the library is built with `force_host` then - // it's ok to be a dylib as the host should always support dylibs. - (false, Some("lib")) - } else { - (true, Some("dylib")) - }; - - let trimmed = rel_ab.trim_end_matches(".rs").to_string(); - - // Normally, every 'extern-private' has a correspodning 'aux-build' - // entry. If so, we remove it from our list of private crates, - // and add an '--extern-private' flag to rustc - if extern_priv.remove_item(&trimmed).is_some() { - add_extern_priv(&trimmed, dylib); - } - - if let Some(crate_type) = crate_type { - aux_rustc.args(&["--crate-type", crate_type]); - } - - aux_rustc.arg("-L").arg(&aux_dir); - - let auxres = aux_cx.compose_and_run( - aux_rustc, - aux_cx.config.compile_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - None, - ); - if !auxres.status.success() { - self.fatal_proc_rec( - &format!( - "auxiliary build of {:?} failed to compile: ", - aux_testpaths.file.display() - ), - &auxres, - ); - } - } - - // Add any '--extern-private' entries without a matching - // 'aux-build' - for private_lib in extern_priv { - add_extern_priv(&private_lib, true); - } - - self.props.unset_rustc_env.clone() - .iter() - .fold(&mut rustc, |rustc, v| rustc.env_remove(v)); - rustc.envs(self.props.rustc_env.clone()); - self.compose_and_run( - rustc, - self.config.compile_lib_path.to_str().unwrap(), - Some(aux_dir.to_str().unwrap()), - input, - ) - } - - fn compose_and_run( - &self, - mut command: Command, - lib_path: &str, - aux_path: Option<&str>, - input: Option, - ) -> ProcRes { - let cmdline = { - let cmdline = self.make_cmdline(&command, lib_path); - logv(self.config, format!("executing {}", cmdline)); - cmdline - }; - - command - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .stdin(Stdio::piped()); - - // Need to be sure to put both the lib_path and the aux path in the dylib - // search path for the child. - let mut path = env::split_paths(&env::var_os(dylib_env_var()).unwrap_or(OsString::new())) - .collect::>(); - if let Some(p) = aux_path { - path.insert(0, PathBuf::from(p)) - } - path.insert(0, PathBuf::from(lib_path)); - - // Add the new dylib search path var - let newpath = env::join_paths(&path).unwrap(); - command.env(dylib_env_var(), newpath); - - let mut child = disable_error_reporting(|| command.spawn()) - .expect(&format!("failed to exec `{:?}`", &command)); - if let Some(input) = input { - child - .stdin - .as_mut() - .unwrap() - .write_all(input.as_bytes()) - .unwrap(); - } - - let Output { - status, - stdout, - stderr, - } = read2_abbreviated(child).expect("failed to read output"); - - let result = ProcRes { - status, - stdout: String::from_utf8_lossy(&stdout).into_owned(), - stderr: String::from_utf8_lossy(&stderr).into_owned(), - cmdline, - }; - - self.dump_output(&result.stdout, &result.stderr); - - result - } - - fn make_compile_args( - &self, - input_file: &Path, - output_file: TargetLocation, - ) -> Command { - let is_rustdoc = self.config.src_base.ends_with("rustdoc-ui") || - self.config.src_base.ends_with("rustdoc-js"); - let mut rustc = if !is_rustdoc { - Command::new(&self.config.rustc_path) - } else { - Command::new( - &self - .config - .rustdoc_path - .clone() - .expect("no rustdoc built yet"), - ) - }; - // FIXME Why is -L here? - rustc.arg(input_file); //.arg("-L").arg(&self.config.build_base); - - // Use a single thread for efficiency and a deterministic error message order - rustc.arg("-Zthreads=1"); - - // Optionally prevent default --target if specified in test compile-flags. - let custom_target = self - .props - .compile_flags - .iter() - .any(|x| x.starts_with("--target")); - - if !custom_target { - let target = if self.props.force_host { - &*self.config.host - } else { - &*self.config.target - }; - - rustc.arg(&format!("--target={}", target)); - } - self.set_revision_flags(&mut rustc); - - if !is_rustdoc { - if let Some(ref incremental_dir) = self.props.incremental_dir { - rustc.args(&["-C", &format!("incremental={}", incremental_dir.display())]); - rustc.args(&["-Z", "incremental-verify-ich"]); - rustc.args(&["-Z", "incremental-queries"]); - } - - if self.config.mode == CodegenUnits { - rustc.args(&["-Z", "human_readable_cgu_names"]); - } - } - - match self.config.mode { - CompileFail | Incremental => { - // If we are extracting and matching errors in the new - // fashion, then you want JSON mode. Old-skool error - // patterns still match the raw compiler output. - if self.props.error_patterns.is_empty() { - rustc.args(&["--error-format", "json"]); - } - if !self.props.disable_ui_testing_normalization { - rustc.arg("-Zui-testing"); - } - } - Ui => { - if !self - .props - .compile_flags - .iter() - .any(|s| s.starts_with("--error-format")) - { - rustc.args(&["--error-format", "json"]); - } - if !self.props.disable_ui_testing_normalization { - rustc.arg("-Zui-testing"); - } - } - MirOpt => { - rustc.args(&[ - "-Zdump-mir=all", - "-Zmir-opt-level=3", - "-Zdump-mir-exclude-pass-number", - ]); - - let mir_dump_dir = self.get_mir_dump_dir(); - let _ = fs::remove_dir_all(&mir_dump_dir); - create_dir_all(mir_dump_dir.as_path()).unwrap(); - let mut dir_opt = "-Zdump-mir-dir=".to_string(); - dir_opt.push_str(mir_dump_dir.to_str().unwrap()); - debug!("dir_opt: {:?}", dir_opt); - - rustc.arg(dir_opt); - } - RunFail | RunPassValgrind | Pretty | DebugInfoCdb | DebugInfoGdbLldb | DebugInfoGdb - | DebugInfoLldb | Codegen | Rustdoc | RunMake | CodegenUnits | JsDocTest | Assembly => { - // do not use JSON output - } - } - - if let Some(PassMode::Check) = self.pass_mode() { - rustc.args(&["--emit", "metadata"]); - } - - if !is_rustdoc { - if self.config.target == "wasm32-unknown-unknown" { - // rustc.arg("-g"); // get any backtrace at all on errors - } else if !self.props.no_prefer_dynamic { - rustc.args(&["-C", "prefer-dynamic"]); - } - } - - match output_file { - TargetLocation::ThisFile(path) => { - rustc.arg("-o").arg(path); - } - TargetLocation::ThisDirectory(path) => { - if is_rustdoc { - // `rustdoc` uses `-o` for the output directory. - rustc.arg("-o").arg(path); - } else { - rustc.arg("--out-dir").arg(path); - } - } - } - - match self.config.compare_mode { - Some(CompareMode::Nll) => { - rustc.args(&["-Zborrowck=mir"]); - } - Some(CompareMode::Polonius) => { - rustc.args(&["-Zpolonius", "-Zborrowck=mir"]); - } - None => {} - } - - if self.props.force_host { - self.maybe_add_external_args(&mut rustc, - self.split_maybe_args(&self.config.host_rustcflags)); - } else { - self.maybe_add_external_args(&mut rustc, - self.split_maybe_args(&self.config.target_rustcflags)); - if !is_rustdoc { - if let Some(ref linker) = self.config.linker { - rustc.arg(format!("-Clinker={}", linker)); - } - } - } - - // Use dynamic musl for tests because static doesn't allow creating dylibs - if self.config.host.contains("musl") { - rustc.arg("-Ctarget-feature=-crt-static"); - } - - rustc.args(&self.props.compile_flags); - - rustc - } - - fn make_exe_name(&self) -> PathBuf { - // Using a single letter here to keep the path length down for - // Windows. Some test names get very long. rustc creates `rcgu` - // files with the module name appended to it which can more than - // double the length. - let mut f = self.output_base_dir().join("a"); - // FIXME: This is using the host architecture exe suffix, not target! - if self.config.target.contains("emscripten") { - f = f.with_extra_extension("js"); - } else if self.config.target.contains("wasm32") { - f = f.with_extra_extension("wasm"); - } else if !env::consts::EXE_SUFFIX.is_empty() { - f = f.with_extra_extension(env::consts::EXE_SUFFIX); - } - f - } - - fn make_run_args(&self) -> ProcArgs { - // If we've got another tool to run under (valgrind), - // then split apart its command - let mut args = self.split_maybe_args(&self.config.runtool); - - // If this is emscripten, then run tests under nodejs - if self.config.target.contains("emscripten") { - if let Some(ref p) = self.config.nodejs { - args.push(p.clone()); - } else { - self.fatal("no NodeJS binary found (--nodejs)"); - } - // If this is otherwise wasm, then run tests under nodejs with our - // shim - } else if self.config.target.contains("wasm32") { - if let Some(ref p) = self.config.nodejs { - args.push(p.clone()); - } else { - self.fatal("no NodeJS binary found (--nodejs)"); - } - - let src = self.config.src_base - .parent().unwrap() // chop off `ui` - .parent().unwrap() // chop off `test` - .parent().unwrap(); // chop off `src` - args.push(src.join("src/etc/wasm32-shim.js").display().to_string()); - } - - let exe_file = self.make_exe_name(); - - // 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(self.split_maybe_args(&self.props.run_flags)); - - let prog = args.remove(0); - ProcArgs { prog, args } - } - - fn split_maybe_args(&self, 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 make_cmdline(&self, command: &Command, libpath: &str) -> String { - use crate::util; - - // Linux and mac don't require adjusting the library search path - if cfg!(unix) { - format!("{:?}", command) - } 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), command) - } - } - - fn dump_output(&self, out: &str, err: &str) { - let revision = if let Some(r) = self.revision { - format!("{}.", r) - } else { - String::new() - }; - - self.dump_output_file(out, &format!("{}out", revision)); - self.dump_output_file(err, &format!("{}err", revision)); - self.maybe_dump_to_stdout(out, err); - } - - fn dump_output_file(&self, out: &str, extension: &str) { - let outfile = self.make_out_name(extension); - fs::write(&outfile, out).unwrap(); - } - - /// Creates a filename for output with the given extension. - /// E.g., `/.../testname.revision.mode/testname.extension`. - fn make_out_name(&self, extension: &str) -> PathBuf { - self.output_base_name().with_extension(extension) - } - - /// Gets the directory where auxiliary files are written. - /// E.g., `/.../testname.revision.mode/auxiliary/`. - fn aux_output_dir_name(&self) -> PathBuf { - self.output_base_dir() - .join("auxiliary") - .with_extra_extension(self.config.mode.disambiguator()) - } - - /// Generates a unique name for the test, such as `testname.revision.mode`. - fn output_testname_unique(&self) -> PathBuf { - output_testname_unique(self.config, self.testpaths, self.safe_revision()) - } - - /// The revision, ignored for incremental compilation since it wants all revisions in - /// the same directory. - fn safe_revision(&self) -> Option<&str> { - if self.config.mode == Incremental { - None - } else { - self.revision - } - } - - /// Gets the absolute path to the directory where all output for the given - /// test/revision should reside. - /// E.g., `/path/to/build/host-triple/test/ui/relative/testname.revision.mode/`. - fn output_base_dir(&self) -> PathBuf { - output_base_dir(self.config, self.testpaths, self.safe_revision()) - } - - /// Gets the absolute path to the base filename used as output for the given - /// test/revision. - /// E.g., `/.../relative/testname.revision.mode/testname`. - fn output_base_name(&self) -> PathBuf { - output_base_name(self.config, self.testpaths, self.safe_revision()) - } - - fn maybe_dump_to_stdout(&self, out: &str, err: &str) { - if self.config.verbose { - println!("------{}------------------------------", "stdout"); - println!("{}", out); - println!("------{}------------------------------", "stderr"); - println!("{}", err); - println!("------------------------------------------"); - } - } - - fn error(&self, err: &str) { - match self.revision { - Some(rev) => println!("\nerror in revision `{}`: {}", rev, err), - None => println!("\nerror: {}", err), - } - } - - fn fatal(&self, err: &str) -> ! { - self.error(err); - error!("fatal error, panic: {:?}", err); - panic!("fatal error"); - } - - fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! { - self.error(err); - proc_res.fatal(None); - } - - // codegen tests (using FileCheck) - - fn compile_test_and_save_ir(&self) -> ProcRes { - let aux_dir = self.aux_output_dir_name(); - - let output_file = TargetLocation::ThisDirectory(self.output_base_dir()); - let mut rustc = self.make_compile_args(&self.testpaths.file, output_file); - rustc.arg("-L").arg(aux_dir).arg("--emit=llvm-ir"); - - self.compose_and_run_compiler(rustc, None) - } - - fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) { - // This works with both `--emit asm` (as default output name for the assembly) - // and `ptx-linker` because the latter can write output at requested location. - let output_path = self.output_base_name().with_extension("s"); - - let output_file = TargetLocation::ThisFile(output_path.clone()); - let mut rustc = self.make_compile_args(&self.testpaths.file, output_file); - - rustc.arg("-L").arg(self.aux_output_dir_name()); - - match self.props.assembly_output.as_ref().map(AsRef::as_ref) { - Some("emit-asm") => { - rustc.arg("--emit=asm"); - } - - Some("ptx-linker") => { - // No extra flags needed. - } - - Some(_) => self.fatal("unknown 'assembly-output' header"), - None => self.fatal("missing 'assembly-output' header"), - } - - (self.compose_and_run_compiler(rustc, None), output_path) - } - - fn verify_with_filecheck(&self, output: &Path) -> ProcRes { - let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap()); - filecheck - .arg("--input-file") - .arg(output) - .arg(&self.testpaths.file); - // It would be more appropriate to make most of the arguments configurable through - // a comment-attribute similar to `compile-flags`. For example, --check-prefixes is a very - // useful flag. - // - // For now, though… - if let Some(rev) = self.revision { - let prefixes = format!("CHECK,{}", rev); - filecheck.args(&["--check-prefixes", &prefixes]); - } - self.compose_and_run(filecheck, "", None, None) - } - - fn run_codegen_test(&self) { - if self.config.llvm_filecheck.is_none() { - self.fatal("missing --llvm-filecheck"); - } - - let proc_res = self.compile_test_and_save_ir(); - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - let output_path = self.output_base_name().with_extension("ll"); - let proc_res = self.verify_with_filecheck(&output_path); - if !proc_res.status.success() { - self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res); - } - } - - fn run_assembly_test(&self) { - if self.config.llvm_filecheck.is_none() { - self.fatal("missing --llvm-filecheck"); - } - - let (proc_res, output_path) = self.compile_test_and_save_assembly(); - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - let proc_res = self.verify_with_filecheck(&output_path); - if !proc_res.status.success() { - self.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 = "freebsd") { - "ISO-8859-1" - } else { - "UTF-8" - } - } - - fn run_rustdoc_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - let out_dir = self.output_base_dir(); - let _ = fs::remove_dir_all(&out_dir); - create_dir_all(&out_dir).unwrap(); - - let proc_res = self.document(&out_dir); - if !proc_res.status.success() { - self.fatal_proc_rec("rustdoc failed!", &proc_res); - } - - if self.props.check_test_line_numbers_match { - self.check_rustdoc_test_option(proc_res); - } else { - let root = self.config.find_rust_src_root().unwrap(); - let res = self.cmd2procres( - Command::new(&self.config.docck_python) - .arg(root.join("src/etc/htmldocck.py")) - .arg(out_dir) - .arg(&self.testpaths.file), - ); - if !res.status.success() { - self.fatal_proc_rec("htmldocck failed!", &res); - } - } - } - - fn get_lines>( - &self, - path: &P, - mut other_files: Option<&mut Vec>, - ) -> Vec { - let content = fs::read_to_string(&path).unwrap(); - let mut ignore = false; - content - .lines() - .enumerate() - .filter_map(|(line_nb, line)| { - if (line.trim_start().starts_with("pub mod ") - || line.trim_start().starts_with("mod ")) - && line.ends_with(';') - { - if let Some(ref mut other_files) = other_files { - other_files.push(line.rsplit("mod ").next().unwrap().replace(";", "")); - } - None - } else { - let sline = line.split("///").last().unwrap_or(""); - let line = sline.trim_start(); - if line.starts_with("```") { - if ignore { - ignore = false; - None - } else { - ignore = true; - Some(line_nb + 1) - } - } else { - None - } - } - }) - .collect() - } - - fn check_rustdoc_test_option(&self, res: ProcRes) { - let mut other_files = Vec::new(); - let mut files: HashMap> = HashMap::new(); - let cwd = env::current_dir().unwrap(); - files.insert( - self.testpaths - .file - .strip_prefix(&cwd) - .unwrap_or(&self.testpaths.file) - .to_str() - .unwrap() - .replace('\\', "/"), - self.get_lines(&self.testpaths.file, Some(&mut other_files)), - ); - for other_file in other_files { - let mut path = self.testpaths.file.clone(); - path.set_file_name(&format!("{}.rs", other_file)); - files.insert( - path.strip_prefix(&cwd) - .unwrap_or(&path) - .to_str() - .unwrap() - .replace('\\', "/"), - self.get_lines(&path, None), - ); - } - - let mut tested = 0; - for _ in res - .stdout - .split('\n') - .filter(|s| s.starts_with("test ")) - .inspect(|s| { - let tmp: Vec<&str> = s.split(" - ").collect(); - if tmp.len() == 2 { - let path = tmp[0].rsplit("test ").next().unwrap(); - if let Some(ref mut v) = files.get_mut(&path.replace('\\', "/")) { - tested += 1; - let mut iter = tmp[1].split("(line "); - iter.next(); - let line = iter - .next() - .unwrap_or(")") - .split(')') - .next() - .unwrap_or("0") - .parse() - .unwrap_or(0); - if let Ok(pos) = v.binary_search(&line) { - v.remove(pos); - } else { - self.fatal_proc_rec( - &format!("Not found doc test: \"{}\" in \"{}\":{:?}", s, path, v), - &res, - ); - } - } - } - }) {} - if tested == 0 { - self.fatal_proc_rec(&format!("No test has been found... {:?}", files), &res); - } else { - for (entry, v) in &files { - if !v.is_empty() { - self.fatal_proc_rec( - &format!( - "Not found test at line{} \"{}\":{:?}", - if v.len() > 1 { "s" } else { "" }, - entry, - v - ), - &res, - ); - } - } - } - } - - fn run_codegen_units_test(&self) { - assert!(self.revision.is_none(), "revisions not relevant here"); - - let proc_res = self.compile_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - self.check_no_compiler_crash(&proc_res); - - const PREFIX: &'static str = "MONO_ITEM "; - const CGU_MARKER: &'static str = "@@"; - - let actual: Vec = proc_res - .stdout - .lines() - .filter(|line| line.starts_with(PREFIX)) - .map(|line| str_to_mono_item(line, true)) - .collect(); - - let expected: Vec = errors::load_errors(&self.testpaths.file, None) - .iter() - .map(|e| str_to_mono_item(&e.msg[..], false)) - .collect(); - - let mut missing = Vec::new(); - let mut wrong_cgus = Vec::new(); - - for expected_item in &expected { - let actual_item_with_same_name = actual.iter().find(|ti| ti.name == expected_item.name); - - if let Some(actual_item) = actual_item_with_same_name { - if !expected_item.codegen_units.is_empty() && - // Also check for codegen units - expected_item.codegen_units != actual_item.codegen_units - { - wrong_cgus.push((expected_item.clone(), actual_item.clone())); - } - } else { - missing.push(expected_item.string.clone()); - } - } - - let unexpected: Vec<_> = actual - .iter() - .filter(|acgu| !expected.iter().any(|ecgu| acgu.name == ecgu.name)) - .map(|acgu| acgu.string.clone()) - .collect(); - - if !missing.is_empty() { - missing.sort(); - - println!("\nThese items should have been contained but were not:\n"); - - for item in &missing { - println!("{}", item); - } - - println!("\n"); - } - - if !unexpected.is_empty() { - let sorted = { - let mut sorted = unexpected.clone(); - sorted.sort(); - sorted - }; - - println!("\nThese items were contained but should not have been:\n"); - - for item in sorted { - println!("{}", item); - } - - println!("\n"); - } - - if !wrong_cgus.is_empty() { - wrong_cgus.sort_by_key(|pair| pair.0.name.clone()); - println!("\nThe following items were assigned to wrong codegen units:\n"); - - for &(ref expected_item, ref actual_item) in &wrong_cgus { - println!("{}", expected_item.name); - println!( - " expected: {}", - codegen_units_to_str(&expected_item.codegen_units) - ); - println!( - " actual: {}", - codegen_units_to_str(&actual_item.codegen_units) - ); - println!(""); - } - } - - if !(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty()) { - panic!(); - } - - #[derive(Clone, Eq, PartialEq)] - struct MonoItem { - name: String, - codegen_units: HashSet, - string: String, - } - - // [MONO_ITEM] name [@@ (cgu)+] - fn str_to_mono_item(s: &str, cgu_has_crate_disambiguator: bool) -> MonoItem { - let s = if s.starts_with(PREFIX) { - (&s[PREFIX.len()..]).trim() - } else { - s.trim() - }; - - let full_string = format!("{}{}", PREFIX, s); - - let parts: Vec<&str> = s - .split(CGU_MARKER) - .map(str::trim) - .filter(|s| !s.is_empty()) - .collect(); - - let name = parts[0].trim(); - - let cgus = if parts.len() > 1 { - let cgus_str = parts[1]; - - cgus_str - .split(' ') - .map(str::trim) - .filter(|s| !s.is_empty()) - .map(|s| { - if cgu_has_crate_disambiguator { - remove_crate_disambiguator_from_cgu(s) - } else { - s.to_string() - } - }) - .collect() - } else { - HashSet::new() - }; - - MonoItem { - name: name.to_owned(), - codegen_units: cgus, - string: full_string, - } - } - - fn codegen_units_to_str(cgus: &HashSet) -> String { - let mut cgus: Vec<_> = cgus.iter().collect(); - cgus.sort(); - - let mut string = String::new(); - for cgu in cgus { - string.push_str(&cgu[..]); - string.push_str(" "); - } - - string - } - - // Given a cgu-name-prefix of the form . or - // the form .-in-., - // remove all crate-disambiguators. - fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String { - lazy_static! { - static ref RE: Regex = Regex::new( - r"^[^\.]+(?P\.[[:alnum:]]+)(-in-[^\.]+(?P\.[[:alnum:]]+))?" - ).unwrap(); - } - - let captures = RE.captures(cgu).unwrap_or_else(|| { - panic!("invalid cgu name encountered: {}", cgu) - }); - - let mut new_name = cgu.to_owned(); - - if let Some(d2) = captures.name("d2") { - new_name.replace_range(d2.start() .. d2.end(), ""); - } - - let d1 = captures.name("d1").unwrap(); - new_name.replace_range(d1.start() .. d1.end(), ""); - - new_name - } - } - - fn init_incremental_test(&self) { - // (See `run_incremental_test` for an overview of how incremental tests work.) - - // Before any of the revisions have executed, create the - // incremental workproduct directory. Delete any old - // incremental work products that may be there from prior - // runs. - let incremental_dir = self.incremental_dir(); - if incremental_dir.exists() { - // Canonicalizing the path will convert it to the //?/ format - // on Windows, which enables paths longer than 260 character - let canonicalized = incremental_dir.canonicalize().unwrap(); - fs::remove_dir_all(canonicalized).unwrap(); - } - fs::create_dir_all(&incremental_dir).unwrap(); - - if self.config.verbose { - print!( - "init_incremental_test: incremental_dir={}", - incremental_dir.display() - ); - } - } - - fn run_incremental_test(&self) { - // Basic plan for a test incremental/foo/bar.rs: - // - load list of revisions rpass1, cfail2, rpass3 - // - each should begin with `rpass`, `cfail`, or `rfail` - // - if `rpass`, expect compile and execution to succeed - // - if `cfail`, expect compilation to fail - // - if `rfail`, expect execution to fail - // - create a directory build/foo/bar.incremental - // - compile foo/bar.rs with -Z incremental=.../foo/bar.incremental and -C rpass1 - // - because name of revision starts with "rpass", expect success - // - compile foo/bar.rs with -Z incremental=.../foo/bar.incremental and -C cfail2 - // - because name of revision starts with "cfail", expect an error - // - load expected errors as usual, but filter for those that end in `[rfail2]` - // - compile foo/bar.rs with -Z incremental=.../foo/bar.incremental and -C rpass3 - // - because name of revision starts with "rpass", expect success - // - execute build/foo/bar.exe and save output - // - // FIXME -- use non-incremental mode as an oracle? That doesn't apply - // to #[rustc_dirty] and clean tests I guess - - let revision = self - .revision - .expect("incremental tests require a list of revisions"); - - // Incremental workproduct directory should have already been created. - let incremental_dir = self.incremental_dir(); - assert!( - incremental_dir.exists(), - "init_incremental_test failed to create incremental dir" - ); - - // Add an extra flag pointing at the incremental directory. - let mut revision_props = self.props.clone(); - revision_props.incremental_dir = Some(incremental_dir); - - let revision_cx = TestCx { - config: self.config, - props: &revision_props, - testpaths: self.testpaths, - revision: self.revision, - }; - - if self.config.verbose { - print!( - "revision={:?} revision_props={:#?}", - revision, revision_props - ); - } - - if revision.starts_with("rpass") { - revision_cx.run_rpass_test(); - } else if revision.starts_with("rfail") { - revision_cx.run_rfail_test(); - } else if revision.starts_with("cfail") { - revision_cx.run_cfail_test(); - } else { - revision_cx.fatal("revision name must begin with rpass, rfail, or cfail"); - } - } - - /// Directory where incremental work products are stored. - fn incremental_dir(&self) -> PathBuf { - self.output_base_name().with_extension("inc") - } - - fn run_rmake_test(&self) { - let cwd = env::current_dir().unwrap(); - let src_root = self - .config - .src_base - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap(); - let src_root = cwd.join(&src_root); - - let tmpdir = cwd.join(self.output_base_name()); - if tmpdir.exists() { - self.aggressive_rm_rf(&tmpdir).unwrap(); - } - create_dir_all(&tmpdir).unwrap(); - - let host = &self.config.host; - let make = if host.contains("dragonfly") - || host.contains("freebsd") - || host.contains("netbsd") - || host.contains("openbsd") - { - "gmake" - } else { - "make" - }; - - let mut cmd = Command::new(make); - cmd.current_dir(&self.testpaths.file) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .env("TARGET", &self.config.target) - .env("PYTHON", &self.config.docck_python) - .env("S", src_root) - .env("RUST_BUILD_STAGE", &self.config.stage_id) - .env("RUSTC", cwd.join(&self.config.rustc_path)) - .env("TMPDIR", &tmpdir) - .env("LD_LIB_PATH_ENVVAR", dylib_env_var()) - .env("HOST_RPATH_DIR", cwd.join(&self.config.compile_lib_path)) - .env("TARGET_RPATH_DIR", cwd.join(&self.config.run_lib_path)) - .env("LLVM_COMPONENTS", &self.config.llvm_components) - .env("LLVM_CXXFLAGS", &self.config.llvm_cxxflags) - - // We for sure don't want these tests to run in parallel, so make - // sure they don't have access to these vars if we run via `make` - // at the top level - .env_remove("MAKEFLAGS") - .env_remove("MFLAGS") - .env_remove("CARGO_MAKEFLAGS"); - - if let Some(ref rustdoc) = self.config.rustdoc_path { - cmd.env("RUSTDOC", cwd.join(rustdoc)); - } - - if let Some(ref node) = self.config.nodejs { - cmd.env("NODE", node); - } - - if let Some(ref linker) = self.config.linker { - cmd.env("RUSTC_LINKER", linker); - } - - if let Some(ref clang) = self.config.run_clang_based_tests_with { - cmd.env("CLANG", clang); - } - - if let Some(ref filecheck) = self.config.llvm_filecheck { - cmd.env("LLVM_FILECHECK", filecheck); - } - - if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir { - cmd.env("LLVM_BIN_DIR", llvm_bin_dir); - } - - // We don't want RUSTFLAGS set from the outside to interfere with - // compiler flags set in the test cases: - cmd.env_remove("RUSTFLAGS"); - - // Use dynamic musl for tests because static doesn't allow creating dylibs - if self.config.host.contains("musl") { - cmd.env("RUSTFLAGS", "-Ctarget-feature=-crt-static") - .env("IS_MUSL_HOST", "1"); - } - - if self.config.target.contains("msvc") && self.config.cc != "" { - // We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe` - // and that `lib.exe` lives next to it. - let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe"); - - // MSYS doesn't like passing flags of the form `/foo` as it thinks it's - // a path and instead passes `C:\msys64\foo`, so convert all - // `/`-arguments to MSVC here to `-` arguments. - let cflags = self - .config - .cflags - .split(' ') - .map(|s| s.replace("/", "-")) - .collect::>() - .join(" "); - - cmd.env("IS_MSVC", "1") - .env("IS_WINDOWS", "1") - .env("MSVC_LIB", format!("'{}' -nologo", lib.display())) - .env("CC", format!("'{}' {}", self.config.cc, cflags)) - .env("CXX", format!("'{}'", &self.config.cxx)); - } else { - cmd.env("CC", format!("{} {}", self.config.cc, self.config.cflags)) - .env("CXX", format!("{} {}", self.config.cxx, self.config.cflags)) - .env("AR", &self.config.ar); - - if self.config.target.contains("windows") { - cmd.env("IS_WINDOWS", "1"); - } - } - - let output = cmd - .spawn() - .and_then(read2_abbreviated) - .expect("failed to spawn `make`"); - if !output.status.success() { - let res = ProcRes { - status: output.status, - stdout: String::from_utf8_lossy(&output.stdout).into_owned(), - stderr: String::from_utf8_lossy(&output.stderr).into_owned(), - cmdline: format!("{:?}", cmd), - }; - self.fatal_proc_rec("make failed", &res); - } - } - - fn aggressive_rm_rf(&self, path: &Path) -> io::Result<()> { - for e in path.read_dir()? { - let entry = e?; - let path = entry.path(); - if entry.file_type()?.is_dir() { - self.aggressive_rm_rf(&path)?; - } else { - // Remove readonly files as well on windows (by default we can't) - fs::remove_file(&path).or_else(|e| { - if cfg!(windows) && e.kind() == io::ErrorKind::PermissionDenied { - let mut meta = entry.metadata()?.permissions(); - meta.set_readonly(false); - fs::set_permissions(&path, meta)?; - fs::remove_file(&path) - } else { - Err(e) - } - })?; - } - } - fs::remove_dir(path) - } - - fn run_js_doc_test(&self) { - if let Some(nodejs) = &self.config.nodejs { - let out_dir = self.output_base_dir(); - - self.document(&out_dir); - - let root = self.config.find_rust_src_root().unwrap(); - let res = self.cmd2procres( - Command::new(&nodejs) - .arg(root.join("src/tools/rustdoc-js/tester.js")) - .arg(out_dir.parent().expect("no parent")) - .arg(&self.testpaths.file.file_stem().expect("couldn't get file stem")), - ); - if !res.status.success() { - self.fatal_proc_rec("rustdoc-js test failed!", &res); - } - } else { - self.fatal("no nodeJS"); - } - } - - fn run_ui_test(&self) { - // if the user specified a format in the ui test - // print the output to the stderr file, otherwise extract - // the rendered error messages from json and print them - let explicit = self - .props - .compile_flags - .iter() - .any(|s| s.contains("--error-format")); - let proc_res = self.compile_test(); - self.check_if_test_should_compile(&proc_res); - - let expected_stderr = self.load_expected_output(UI_STDERR); - let expected_stdout = self.load_expected_output(UI_STDOUT); - let expected_fixed = self.load_expected_output(UI_FIXED); - - let normalized_stdout = - self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout); - - let stderr = if explicit { - proc_res.stderr.clone() - } else { - json::extract_rendered(&proc_res.stderr) - }; - - let normalized_stderr = self.normalize_output(&stderr, &self.props.normalize_stderr); - - let mut errors = 0; - if !self.props.dont_check_compiler_stdout { - errors += self.compare_output("stdout", &normalized_stdout, &expected_stdout); - } - if !self.props.dont_check_compiler_stderr { - errors += self.compare_output("stderr", &normalized_stderr, &expected_stderr); - } - - let modes_to_prune = vec![CompareMode::Nll]; - self.prune_duplicate_outputs(&modes_to_prune); - - if self.config.compare_mode.is_some() { - // don't test rustfix with nll right now - } else if self.config.rustfix_coverage { - // Find out which tests have `MachineApplicable` suggestions but are missing - // `run-rustfix` or `run-rustfix-only-machine-applicable` headers. - // - // This will return an empty `Vec` in case the executed test file has a - // `compile-flags: --error-format=xxxx` header with a value other than `json`. - let suggestions = get_suggestions_from_json( - &proc_res.stderr, - &HashSet::new(), - Filter::MachineApplicableOnly - ).unwrap_or_default(); - if suggestions.len() > 0 - && !self.props.run_rustfix - && !self.props.rustfix_only_machine_applicable { - let mut coverage_file_path = self.config.build_base.clone(); - coverage_file_path.push("rustfix_missing_coverage.txt"); - debug!("coverage_file_path: {}", coverage_file_path.display()); - - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(coverage_file_path.as_path()) - .expect("could not create or open file"); - - if let Err(_) = writeln!(file, "{}", self.testpaths.file.display()) { - panic!("couldn't write to {}", coverage_file_path.display()); - } - } - } else if self.props.run_rustfix { - // Apply suggestions from rustc to the code itself - let unfixed_code = self - .load_expected_output_from_path(&self.testpaths.file) - .unwrap(); - let suggestions = get_suggestions_from_json( - &proc_res.stderr, - &HashSet::new(), - if self.props.rustfix_only_machine_applicable { - Filter::MachineApplicableOnly - } else { - Filter::Everything - }, - ).unwrap(); - let fixed_code = apply_suggestions(&unfixed_code, &suggestions).expect(&format!( - "failed to apply suggestions for {:?} with rustfix", - self.testpaths.file - )); - - errors += self.compare_output("fixed", &fixed_code, &expected_fixed); - } else if !expected_fixed.is_empty() { - panic!( - "the `// run-rustfix` directive wasn't found but a `*.fixed` \ - file was found" - ); - } - - if errors > 0 { - println!("To update references, rerun the tests and pass the `--bless` flag"); - let relative_path_to_file = self - .testpaths - .relative_dir - .join(self.testpaths.file.file_name().unwrap()); - println!( - "To only update this specific test, also pass `--test-args {}`", - relative_path_to_file.display(), - ); - self.fatal_proc_rec( - &format!("{} errors occurred comparing output.", errors), - &proc_res, - ); - } - - let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); - - if self.should_run_successfully() { - let proc_res = self.exec_compiled_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - } - - debug!("run_ui_test: explicit={:?} config.compare_mode={:?} expected_errors={:?} \ - proc_res.status={:?} props.error_patterns={:?}", - explicit, self.config.compare_mode, expected_errors, proc_res.status, - self.props.error_patterns); - if !explicit && self.config.compare_mode.is_none() { - if !proc_res.status.success() { - if !self.props.error_patterns.is_empty() { - // "// error-pattern" comments - self.check_error_patterns(&proc_res.stderr, &proc_res); - } else { - // "//~ERROR comments" - self.check_expected_errors(expected_errors, &proc_res); - } - } - } - - if self.props.run_rustfix && self.config.compare_mode.is_none() { - // And finally, compile the fixed code and make sure it both - // succeeds and has no diagnostics. - let mut rustc = self.make_compile_args( - &self.testpaths.file.with_extension(UI_FIXED), - TargetLocation::ThisFile(self.make_exe_name()), - ); - rustc.arg("-L").arg(&self.aux_output_dir_name()); - let res = self.compose_and_run_compiler(rustc, None); - if !res.status.success() { - self.fatal_proc_rec("failed to compile fixed code", &res); - } - if !res.stderr.is_empty() && !self.props.rustfix_only_machine_applicable { - self.fatal_proc_rec("fixed code is still producing diagnostics", &res); - } - } - } - - fn run_mir_opt_test(&self) { - let proc_res = self.compile_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("compilation failed!", &proc_res); - } - - let proc_res = self.exec_compiled_test(); - - if !proc_res.status.success() { - self.fatal_proc_rec("test run failed!", &proc_res); - } - self.check_mir_dump(); - } - - fn check_mir_dump(&self) { - let test_file_contents = fs::read_to_string(&self.testpaths.file).unwrap(); - if let Some(idx) = test_file_contents.find("// END RUST SOURCE") { - let (_, tests_text) = test_file_contents.split_at(idx + "// END_RUST SOURCE".len()); - let tests_text_str = String::from(tests_text); - let mut curr_test: Option<&str> = None; - let mut curr_test_contents = vec![ExpectedLine::Elision]; - for l in tests_text_str.lines() { - debug!("line: {:?}", l); - if l.starts_with("// START ") { - let (_, t) = l.split_at("// START ".len()); - curr_test = Some(t); - } else if l.starts_with("// END") { - let (_, t) = l.split_at("// END ".len()); - if Some(t) != curr_test { - panic!("mismatched START END test name"); - } - self.compare_mir_test_output(curr_test.unwrap(), &curr_test_contents); - curr_test = None; - curr_test_contents.clear(); - curr_test_contents.push(ExpectedLine::Elision); - } else if l.is_empty() { - // ignore - } else if l.starts_with("//") && l.split_at("//".len()).1.trim() == "..." { - curr_test_contents.push(ExpectedLine::Elision) - } else if l.starts_with("// ") { - let (_, test_content) = l.split_at("// ".len()); - curr_test_contents.push(ExpectedLine::Text(test_content)); - } - } - } - } - - fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Path) { - let t = |file| fs::metadata(file).unwrap().modified().unwrap(); - let source_file = &self.testpaths.file; - let output_time = t(output_file); - let source_time = t(source_file); - if source_time > output_time { - debug!( - "source file time: {:?} output file time: {:?}", - source_time, output_time - ); - panic!( - "test source file `{}` is newer than potentially stale output file `{}`.", - source_file.display(), - test_name - ); - } - } - - fn compare_mir_test_output(&self, test_name: &str, expected_content: &[ExpectedLine<&str>]) { - let mut output_file = PathBuf::new(); - output_file.push(self.get_mir_dump_dir()); - output_file.push(test_name); - debug!("comparing the contests of: {:?}", output_file); - debug!("with: {:?}", expected_content); - if !output_file.exists() { - panic!( - "Output file `{}` from test does not exist", - output_file.into_os_string().to_string_lossy() - ); - } - self.check_mir_test_timestamp(test_name, &output_file); - - let dumped_string = fs::read_to_string(&output_file).unwrap(); - let mut dumped_lines = dumped_string - .lines() - .map(|l| nocomment_mir_line(l)) - .filter(|l| !l.is_empty()); - let mut expected_lines = expected_content - .iter() - .filter(|&l| { - if let &ExpectedLine::Text(l) = l { - !l.is_empty() - } else { - true - } - }) - .peekable(); - - let compare = |expected_line, dumped_line| { - let e_norm = normalize_mir_line(expected_line); - let d_norm = normalize_mir_line(dumped_line); - debug!("found: {:?}", d_norm); - debug!("expected: {:?}", e_norm); - e_norm == d_norm - }; - - let error = |expected_line, extra_msg| { - let normalize_all = dumped_string - .lines() - .map(nocomment_mir_line) - .filter(|l| !l.is_empty()) - .collect::>() - .join("\n"); - let f = |l: &ExpectedLine<_>| match l { - &ExpectedLine::Elision => "... (elided)".into(), - &ExpectedLine::Text(t) => t, - }; - let expected_content = expected_content - .iter() - .map(|l| f(l)) - .collect::>() - .join("\n"); - panic!( - "Did not find expected line, error: {}\n\ - Expected Line: {:?}\n\ - Test Name: {}\n\ - Expected:\n{}\n\ - Actual:\n{}", - extra_msg, expected_line, test_name, expected_content, normalize_all - ); - }; - - // We expect each non-empty line to appear consecutively, non-consecutive lines - // must be separated by at least one Elision - let mut start_block_line = None; - while let Some(dumped_line) = dumped_lines.next() { - match expected_lines.next() { - Some(&ExpectedLine::Text(expected_line)) => { - let normalized_expected_line = normalize_mir_line(expected_line); - if normalized_expected_line.contains(":{") { - start_block_line = Some(expected_line); - } - - if !compare(expected_line, dumped_line) { - error!("{:?}", start_block_line); - error( - expected_line, - format!( - "Mismatch in lines\n\ - Current block: {}\n\ - Actual Line: {:?}", - start_block_line.unwrap_or("None"), - dumped_line - ), - ); - } - } - Some(&ExpectedLine::Elision) => { - // skip any number of elisions in a row. - while let Some(&&ExpectedLine::Elision) = expected_lines.peek() { - expected_lines.next(); - } - if let Some(&ExpectedLine::Text(expected_line)) = expected_lines.next() { - let mut found = compare(expected_line, dumped_line); - if found { - continue; - } - while let Some(dumped_line) = dumped_lines.next() { - found = compare(expected_line, dumped_line); - if found { - break; - } - } - if !found { - error(expected_line, "ran out of mir dump to match against".into()); - } - } - } - None => {} - } - } - } - - fn get_mir_dump_dir(&self) -> PathBuf { - let mut mir_dump_dir = PathBuf::from(self.config.build_base.as_path()); - debug!("input_file: {:?}", self.testpaths.file); - mir_dump_dir.push(&self.testpaths.relative_dir); - mir_dump_dir.push(self.testpaths.file.file_stem().unwrap()); - mir_dump_dir - } - - fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String { - let cflags = self.props.compile_flags.join(" "); - let json = cflags.contains("--error-format json") - || cflags.contains("--error-format pretty-json") - || cflags.contains("--error-format=json") - || cflags.contains("--error-format=pretty-json"); - - let mut normalized = output.to_string(); - - let mut normalize_path = |from: &Path, to: &str| { - let mut from = from.display().to_string(); - if json { - from = from.replace("\\", "\\\\"); - } - normalized = normalized.replace(&from, to); - }; - - let parent_dir = self.testpaths.file.parent().unwrap(); - normalize_path(parent_dir, "$DIR"); - - // Paths into the libstd/libcore - let src_dir = self.config.src_base.parent().unwrap().parent().unwrap(); - normalize_path(src_dir, "$SRC_DIR"); - - // Paths into the build directory - let test_build_dir = &self.config.build_base; - let parent_build_dir = test_build_dir.parent().unwrap().parent().unwrap().parent().unwrap(); - - // eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui - normalize_path(test_build_dir, "$TEST_BUILD_DIR"); - // eg. /home/user/rust/build - normalize_path(parent_build_dir, "$BUILD_DIR"); - - // Paths into lib directory. - normalize_path(&parent_build_dir.parent().unwrap().join("lib"), "$LIB_DIR"); - - if json { - // escaped newlines in json strings should be readable - // in the stderr files. There's no point int being correct, - // since only humans process the stderr files. - // Thus we just turn escaped newlines back into newlines. - normalized = normalized.replace("\\n", "\n"); - } - - // If there are `$SRC_DIR` normalizations with line and column numbers, then replace them - // with placeholders as we do not want tests needing updated when compiler source code - // changes. - // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL - normalized = Regex::new("SRC_DIR(.+):\\d+:\\d+").unwrap() - .replace_all(&normalized, "SRC_DIR$1:LL:COL").into_owned(); - - normalized = Self::normalize_platform_differences(&normalized); - normalized = normalized.replace("\t", "\\t"); // makes tabs visible - - // Remove test annotations like `//~ ERROR text` from the output, - // since they duplicate actual errors and make the output hard to read. - normalized = Regex::new("\\s*//(\\[.*\\])?~.*").unwrap() - .replace_all(&normalized, "").into_owned(); - - for rule in custom_rules { - let re = Regex::new(&rule.0).expect("bad regex in custom normalization rule"); - normalized = re.replace_all(&normalized, &rule.1[..]).into_owned(); - } - normalized - } - - /// Normalize output differences across platforms. Generally changes Windows output to be more - /// Unix-like. - /// - /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings - /// with LF. - fn normalize_platform_differences(output: &str) -> String { - lazy_static! { - /// Used to find Windows paths. - /// - /// It's not possible to detect paths in the error messages generally, but this is a - /// decent enough heuristic. - static ref PATH_BACKSLASH_RE: Regex = Regex::new(r#"(?x) - (?: - # Match paths that don't include spaces. - (?:\\[\pL\pN\.\-_']+)+\.\pL+ - | - # If the path starts with a well-known root, then allow spaces. - \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_' ]+)+ - )"# - ).unwrap(); - } - - let output = output.replace(r"\\", r"\"); - - PATH_BACKSLASH_RE.replace_all(&output, |caps: &Captures<'_>| { - println!("{}", &caps[0]); - caps[0].replace(r"\", "/") - }).replace("\r\n", "\n") - } - - fn expected_output_path(&self, kind: &str) -> PathBuf { - let mut path = expected_output_path( - &self.testpaths, - self.revision, - &self.config.compare_mode, - kind, - ); - - if !path.exists() { - if let Some(CompareMode::Polonius) = self.config.compare_mode { - path = expected_output_path( - &self.testpaths, - self.revision, - &Some(CompareMode::Nll), - kind, - ); - } - } - - if !path.exists() { - path = expected_output_path(&self.testpaths, self.revision, &None, kind); - } - - path - } - - fn load_expected_output(&self, kind: &str) -> String { - let path = self.expected_output_path(kind); - if path.exists() { - match self.load_expected_output_from_path(&path) { - Ok(x) => x, - Err(x) => self.fatal(&x), - } - } else { - String::new() - } - } - - fn load_expected_output_from_path(&self, path: &Path) -> Result { - fs::read_to_string(path).map_err(|err| { - format!("failed to load expected output from `{}`: {}", path.display(), err) - }) - } - - fn delete_file(&self, file: &PathBuf) { - if let Err(e) = fs::remove_file(file) { - self.fatal(&format!( - "failed to delete `{}`: {}", - file.display(), - e, - )); - } - } - - fn compare_output(&self, kind: &str, actual: &str, expected: &str) -> usize { - if actual == expected { - return 0; - } - - if !self.config.bless { - if expected.is_empty() { - println!("normalized {}:\n{}\n", kind, actual); - } else { - println!("diff of {}:\n", kind); - let diff_results = make_diff(expected, actual, 3); - for result in diff_results { - let mut line_number = result.line_number; - for line in result.lines { - match line { - DiffLine::Expected(e) => { - println!("-\t{}", e); - line_number += 1; - } - DiffLine::Context(c) => { - println!("{}\t{}", line_number, c); - line_number += 1; - } - DiffLine::Resulting(r) => { - println!("+\t{}", r); - } - } - } - println!(""); - } - } - } - - let mode = self.config.compare_mode.as_ref().map_or("", |m| m.to_str()); - let output_file = self - .output_base_name() - .with_extra_extension(self.revision.unwrap_or("")) - .with_extra_extension(mode) - .with_extra_extension(kind); - - let mut files = vec![output_file]; - if self.config.bless { - files.push(expected_output_path( - self.testpaths, - self.revision, - &self.config.compare_mode, - kind, - )); - } - - for output_file in &files { - if actual.is_empty() { - self.delete_file(output_file); - } else if let Err(err) = fs::write(&output_file, &actual) { - self.fatal(&format!( - "failed to write {} to `{}`: {}", - kind, - output_file.display(), - err, - )); - } - } - - println!("\nThe actual {0} differed from the expected {0}.", kind); - for output_file in files { - println!("Actual {} saved to {}", kind, output_file.display()); - } - if self.config.bless { - 0 - } else { - 1 - } - } - - fn prune_duplicate_output(&self, mode: CompareMode, kind: &str, canon_content: &str) { - let examined_path = expected_output_path( - &self.testpaths, - self.revision, - &Some(mode), - kind, - ); - - let examined_content = self - .load_expected_output_from_path(&examined_path) - .unwrap_or_else(|_| String::new()); - - if examined_path.exists() && canon_content == &examined_content { - self.delete_file(&examined_path); - } - } - - fn prune_duplicate_outputs(&self, modes: &[CompareMode]) { - if self.config.bless { - for kind in UI_EXTENSIONS { - let canon_comparison_path = expected_output_path( - &self.testpaths, - self.revision, - &None, - kind, - ); - - if let Ok(canon) = self.load_expected_output_from_path(&canon_comparison_path) { - for mode in modes { - self.prune_duplicate_output(mode.clone(), kind, &canon); - } - } - } - } - } - - fn create_stamp(&self) { - let stamp = crate::stamp(&self.config, self.testpaths, self.revision); - fs::write(&stamp, compute_stamp_hash(&self.config)).unwrap(); - } -} - -struct ProcArgs { - prog: String, - args: Vec, -} - -pub struct ProcRes { - status: ExitStatus, - stdout: String, - stderr: String, - cmdline: String, -} - -impl ProcRes { - pub fn fatal(&self, err: Option<&str>) -> ! { - if let Some(e) = err { - println!("\nerror: {}", e); - } - print!( - "\ - status: {}\n\ - command: {}\n\ - stdout:\n\ - ------------------------------------------\n\ - {}\n\ - ------------------------------------------\n\ - stderr:\n\ - ------------------------------------------\n\ - {}\n\ - ------------------------------------------\n\ - \n", - self.status, self.cmdline, - json::extract_rendered(&self.stdout), - json::extract_rendered(&self.stderr), - ); - // Use resume_unwind instead of panic!() to prevent a panic message + backtrace from - // compiletest, which is unnecessary noise. - std::panic::resume_unwind(Box::new(())); - } -} - -enum TargetLocation { - ThisFile(PathBuf), - ThisDirectory(PathBuf), -} - -#[derive(Clone, PartialEq, Eq)] -enum ExpectedLine> { - Elision, - Text(T), -} - -impl fmt::Debug for ExpectedLine -where - T: AsRef + fmt::Debug, -{ - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - if let &ExpectedLine::Text(ref t) = self { - write!(formatter, "{:?}", t) - } else { - write!(formatter, "\"...\" (Elision)") - } - } -} - -fn normalize_mir_line(line: &str) -> String { - nocomment_mir_line(line).replace(char::is_whitespace, "") -} - -fn nocomment_mir_line(line: &str) -> &str { - if let Some(idx) = line.find("//") { - let (l, _) = line.split_at(idx); - l.trim_end() - } else { - line - } -} - -fn read2_abbreviated(mut child: Child) -> io::Result { - use crate::read2::read2; - use std::mem::replace; - - const HEAD_LEN: usize = 160 * 1024; - const TAIL_LEN: usize = 256 * 1024; - - enum ProcOutput { - Full(Vec), - Abbreviated { - head: Vec, - skipped: usize, - tail: Box<[u8]>, - }, - } - - impl ProcOutput { - fn extend(&mut self, data: &[u8]) { - let new_self = match *self { - ProcOutput::Full(ref mut bytes) => { - bytes.extend_from_slice(data); - let new_len = bytes.len(); - if new_len <= HEAD_LEN + TAIL_LEN { - return; - } - let tail = bytes.split_off(new_len - TAIL_LEN).into_boxed_slice(); - let head = replace(bytes, Vec::new()); - let skipped = new_len - HEAD_LEN - TAIL_LEN; - ProcOutput::Abbreviated { - head, - skipped, - tail, - } - } - ProcOutput::Abbreviated { - ref mut skipped, - ref mut tail, - .. - } => { - *skipped += data.len(); - if data.len() <= TAIL_LEN { - tail[..data.len()].copy_from_slice(data); - tail.rotate_left(data.len()); - } else { - tail.copy_from_slice(&data[(data.len() - TAIL_LEN)..]); - } - return; - } - }; - *self = new_self; - } - - fn into_bytes(self) -> Vec { - match self { - ProcOutput::Full(bytes) => bytes, - ProcOutput::Abbreviated { - mut head, - skipped, - tail, - } => { - write!(&mut head, "\n\n<<<<<< SKIPPED {} BYTES >>>>>>\n\n", skipped).unwrap(); - head.extend_from_slice(&tail); - head - } - } - } - } - - let mut stdout = ProcOutput::Full(Vec::new()); - let mut stderr = ProcOutput::Full(Vec::new()); - - drop(child.stdin.take()); - read2( - child.stdout.take().unwrap(), - child.stderr.take().unwrap(), - &mut |is_stdout, data, _| { - if is_stdout { &mut stdout } else { &mut stderr }.extend(data); - data.clear(); - }, - )?; - let status = child.wait()?; - - Ok(Output { - status, - stdout: stdout.into_bytes(), - stderr: stderr.into_bytes(), - }) -} diff --git a/src/tools/compiletest/src/runtest/tests.rs b/src/tools/compiletest/src/runtest/tests.rs deleted file mode 100644 index 79128aa9c696a..0000000000000 --- a/src/tools/compiletest/src/runtest/tests.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::*; - -#[test] -fn normalize_platform_differences() { - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\foo.rs"), - "$DIR/foo.rs" - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$BUILD_DIR\..\parser.rs"), - "$BUILD_DIR/../parser.rs" - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\bar.rs hello\nworld"), - r"$DIR/bar.rs hello\nworld" - ); - assert_eq!( - TestCx::normalize_platform_differences(r"either bar\baz.rs or bar\baz\mod.rs"), - r"either bar/baz.rs or bar/baz/mod.rs", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"`.\some\path.rs`"), - r"`./some/path.rs`", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"`some\path.rs`"), - r"`some/path.rs`", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\path-with-dashes.rs"), - r"$DIR/path-with-dashes.rs" - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\path_with_underscores.rs"), - r"$DIR/path_with_underscores.rs", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\foo.rs:12:11"), "$DIR/foo.rs:12:11", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\path with spaces 'n' quotes"), - "$DIR/path with spaces 'n' quotes", - ); - assert_eq!( - TestCx::normalize_platform_differences(r"$DIR\file_with\no_extension"), - "$DIR/file_with/no_extension", - ); - - assert_eq!(TestCx::normalize_platform_differences(r"\n"), r"\n"); - assert_eq!(TestCx::normalize_platform_differences(r"{ \n"), r"{ \n"); - assert_eq!(TestCx::normalize_platform_differences(r"`\]`"), r"`\]`"); - assert_eq!(TestCx::normalize_platform_differences(r#""\{""#), r#""\{""#); - assert_eq!( - TestCx::normalize_platform_differences(r#"write!(&mut v, "Hello\n")"#), - r#"write!(&mut v, "Hello\n")"# - ); - assert_eq!( - TestCx::normalize_platform_differences(r#"println!("test\ntest")"#), - r#"println!("test\ntest")"#, - ); -} diff --git a/src/tools/compiletest/src/tests.rs b/src/tools/compiletest/src/tests.rs deleted file mode 100644 index 388ad75757f61..0000000000000 --- a/src/tools/compiletest/src/tests.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::*; - -#[test] -fn test_extract_gdb_version() { - macro_rules! test { ($($expectation:tt: $input:tt,)*) => {{$( - assert_eq!(extract_gdb_version($input), Some($expectation)); - )*}}} - - test! { - 7000001: "GNU gdb (GDB) CentOS (7.0.1-45.el5.centos)", - - 7002000: "GNU gdb (GDB) Red Hat Enterprise Linux (7.2-90.el6)", - - 7004000: "GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04", - 7004001: "GNU gdb (GDB) 7.4.1-debian", - - 7006001: "GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7", - - 7007001: "GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1", - 7007001: "GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1", - 7007001: "GNU gdb (GDB) Fedora 7.7.1-21.fc20", - - 7008000: "GNU gdb (GDB; openSUSE 13.2) 7.8", - 7009001: "GNU gdb (GDB) Fedora 7.9.1-20.fc22", - 7010001: "GNU gdb (GDB) Fedora 7.10.1-31.fc23", - - 7011000: "GNU gdb (Ubuntu 7.11-0ubuntu1) 7.11", - 7011001: "GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1", - 7011001: "GNU gdb (Debian 7.11.1-2) 7.11.1", - 7011001: "GNU gdb (GDB) Fedora 7.11.1-86.fc24", - 7011001: "GNU gdb (GDB; openSUSE Leap 42.1) 7.11.1", - 7011001: "GNU gdb (GDB; openSUSE Tumbleweed) 7.11.1", - - 7011090: "7.11.90", - 7011090: "GNU gdb (Ubuntu 7.11.90.20161005-0ubuntu1) 7.11.90.20161005-git", - - 7012000: "7.12", - 7012000: "GNU gdb (GDB) 7.12", - 7012000: "GNU gdb (GDB) 7.12.20161027-git", - 7012050: "GNU gdb (GDB) 7.12.50.20161027-git", - } -} - -#[test] -fn is_test_test() { - assert_eq!(true, is_test(&OsString::from("a_test.rs"))); - assert_eq!(false, is_test(&OsString::from(".a_test.rs"))); - assert_eq!(false, is_test(&OsString::from("a_cat.gif"))); - assert_eq!(false, is_test(&OsString::from("#a_dog_gif"))); - assert_eq!(false, is_test(&OsString::from("~a_temp_file"))); -} diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs deleted file mode 100644 index 56ebea7c20f36..0000000000000 --- a/src/tools/compiletest/src/util.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::ffi::OsStr; -use std::env; -use std::path::PathBuf; -use crate::common::Config; - -use log::*; - -#[cfg(test)] -mod tests; - -/// Conversion table from triple OS name to Rust SYSNAME -const OS_TABLE: &'static [(&'static str, &'static str)] = &[ - ("android", "android"), - ("androideabi", "android"), - ("cloudabi", "cloudabi"), - ("cuda", "cuda"), - ("darwin", "macos"), - ("dragonfly", "dragonfly"), - ("emscripten", "emscripten"), - ("freebsd", "freebsd"), - ("fuchsia", "fuchsia"), - ("haiku", "haiku"), - ("hermit", "hermit"), - ("ios", "ios"), - ("l4re", "l4re"), - ("linux", "linux"), - ("mingw32", "windows"), - ("none", "none"), - ("netbsd", "netbsd"), - ("openbsd", "openbsd"), - ("redox", "redox"), - ("sgx", "sgx"), - ("solaris", "solaris"), - ("win32", "windows"), - ("windows", "windows"), - ("vxworks", "vxworks"), -]; - -const ARCH_TABLE: &'static [(&'static str, &'static str)] = &[ - ("aarch64", "aarch64"), - ("amd64", "x86_64"), - ("arm", "arm"), - ("arm64", "aarch64"), - ("armv4t", "arm"), - ("armv5te", "arm"), - ("armv7", "arm"), - ("armv7s", "arm"), - ("asmjs", "asmjs"), - ("hexagon", "hexagon"), - ("i386", "x86"), - ("i586", "x86"), - ("i686", "x86"), - ("mips", "mips"), - ("mips64", "mips64"), - ("mips64el", "mips64"), - ("mipsisa32r6", "mips"), - ("mipsisa32r6el", "mips"), - ("mipsisa64r6", "mips64"), - ("mipsisa64r6el", "mips64"), - ("mipsel", "mips"), - ("mipsisa32r6", "mips"), - ("mipsisa32r6el", "mips"), - ("mipsisa64r6", "mips64"), - ("mipsisa64r6el", "mips64"), - ("msp430", "msp430"), - ("nvptx64", "nvptx64"), - ("powerpc", "powerpc"), - ("powerpc64", "powerpc64"), - ("powerpc64le", "powerpc64"), - ("s390x", "s390x"), - ("sparc", "sparc"), - ("sparc64", "sparc64"), - ("sparcv9", "sparc64"), - ("thumbv6m", "thumb"), - ("thumbv7em", "thumb"), - ("thumbv7m", "thumb"), - ("wasm32", "wasm32"), - ("x86_64", "x86_64"), - ("xcore", "xcore"), -]; - -pub fn matches_os(triple: &str, name: &str) -> bool { - // For the wasm32 bare target we ignore anything also ignored on emscripten - // and then we also recognize `wasm32-bare` as the os for the target - if triple == "wasm32-unknown-unknown" { - return name == "emscripten" || name == "wasm32-bare"; - } - let triple: Vec<_> = triple.split('-').collect(); - for &(triple_os, os) in OS_TABLE { - if triple.contains(&triple_os) { - return os == name; - } - } - panic!("Cannot determine OS from triple"); -} - -/// Determine the architecture from `triple` -pub fn get_arch(triple: &str) -> &'static str { - let triple: Vec<_> = triple.split('-').collect(); - for &(triple_arch, arch) in ARCH_TABLE { - if triple.contains(&triple_arch) { - return arch; - } - } - panic!("Cannot determine Architecture from triple"); -} - -pub fn get_env(triple: &str) -> Option<&str> { - triple.split('-').nth(3) -} - -pub fn get_pointer_width(triple: &str) -> &'static str { - if (triple.contains("64") && !triple.ends_with("gnux32")) || triple.starts_with("s390x") { - "64bit" - } else { - "32bit" - } -} - -pub fn make_new_path(path: &str) -> String { - assert!(cfg!(windows)); - // Windows just uses PATH as the library search path, so we have to - // maintain the current value while adding our own - match env::var(lib_path_env_var()) { - Ok(curr) => format!("{}{}{}", path, path_div(), curr), - Err(..) => path.to_owned(), - } -} - -pub fn lib_path_env_var() -> &'static str { - "PATH" -} -fn path_div() -> &'static str { - ";" -} - -pub fn logv(config: &Config, s: String) { - debug!("{}", s); - if config.verbose { - println!("{}", s); - } -} - -pub trait PathBufExt { - /// Append an extension to the path, even if it already has one. - fn with_extra_extension>(&self, extension: S) -> PathBuf; -} - -impl PathBufExt for PathBuf { - fn with_extra_extension>(&self, extension: S) -> PathBuf { - if extension.as_ref().len() == 0 { - self.clone() - } else { - let mut fname = self.file_name().unwrap().to_os_string(); - if !extension.as_ref().to_str().unwrap().starts_with(".") { - fname.push("."); - } - fname.push(extension); - self.with_file_name(fname) - } - } -} diff --git a/src/tools/compiletest/src/util/tests.rs b/src/tools/compiletest/src/util/tests.rs deleted file mode 100644 index 55bf659ba28f1..0000000000000 --- a/src/tools/compiletest/src/util/tests.rs +++ /dev/null @@ -1,32 +0,0 @@ -use super::*; - -#[test] -#[should_panic(expected = "Cannot determine Architecture from triple")] -fn test_get_arch_failure() { - get_arch("abc"); -} - -#[test] -fn test_get_arch() { - assert_eq!("x86_64", get_arch("x86_64-unknown-linux-gnu")); - assert_eq!("x86_64", get_arch("amd64")); - assert_eq!("nvptx64", get_arch("nvptx64-nvidia-cuda")); -} - -#[test] -#[should_panic(expected = "Cannot determine OS from triple")] -fn test_matches_os_failure() { - matches_os("abc", "abc"); -} - -#[test] -fn test_matches_os() { - assert!(matches_os("x86_64-unknown-linux-gnu", "linux")); - assert!(matches_os("wasm32-unknown-unknown", "emscripten")); - assert!(matches_os("wasm32-unknown-unknown", "wasm32-bare")); - assert!(!matches_os("wasm32-unknown-unknown", "windows")); - assert!(matches_os("thumbv6m0-none-eabi", "none")); - assert!(matches_os("riscv32imc-unknown-none-elf", "none")); - assert!(matches_os("nvptx64-nvidia-cuda", "cuda")); - assert!(matches_os("x86_64-fortanix-unknown-sgx", "sgx")); -} diff --git a/src/tools/tidy/src/extdeps.rs b/src/tools/tidy/src/extdeps.rs index 52e263df5e3d3..fd3ae191a6fbe 100644 --- a/src/tools/tidy/src/extdeps.rs +++ b/src/tools/tidy/src/extdeps.rs @@ -6,6 +6,7 @@ use std::path::Path; /// List of whitelisted sources for packages. const WHITELISTED_SOURCES: &[&str] = &[ "\"registry+https://github.com/rust-lang/crates.io-index\"", + "\"git+https://github.com/djrenren/libtest.git#3824d622c489ba314a0fa355fc47779a8251b235\"" ]; /// Checks for external package sources.