diff --git a/Cargo.toml b/Cargo.toml index 4b795c7..a1e76f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" [lib] name = "compiletest_rs" +edition = "2018" [dependencies] diff = "0.1.10" @@ -26,6 +27,8 @@ serde_json = "1.0" serde_derive = "1.0" rustfix = "0.4.1" tester = "0.7" +lazy_static = "1.0" +walkdir = "2" [target."cfg(unix)".dependencies] libc = "0.2" diff --git a/src/common.rs b/src/common.rs index 898a443..13b6f50 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,33 +1,24 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. pub use self::Mode::*; use std::env; +use std::ffi::OsString; use std::fmt; use std::fs::{read_dir, remove_file}; +use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::path::PathBuf; -#[cfg(feature = "rustc")] -use rustc; use test::ColorConfig; -use runtest::dylib_env_var; +use crate::util::PathBufExt; +use crate::runtest::dylib_env_var; #[derive(Clone, Copy, PartialEq, Debug)] pub enum Mode { CompileFail, - ParseFail, RunFail, - RunPass, RunPassValgrind, Pretty, + DebugInfoCdb, + DebugInfoGdbLldb, DebugInfoGdb, DebugInfoLldb, Codegen, @@ -36,16 +27,19 @@ pub enum Mode { Incremental, RunMake, Ui, + JsDocTest, MirOpt, + Assembly, } impl Mode { pub fn disambiguator(self) -> &'static str { - // Run-pass and pretty run-pass tests could run concurrently, and if they do, + // 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 both on gdb and lldb. + // can be run on cdb, gdb, and lldb. match self { Pretty => ".pretty", + DebugInfoCdb => ".cdb", DebugInfoGdb => ".gdb", DebugInfoLldb => ".lldb", _ => "", @@ -58,11 +52,11 @@ impl FromStr for Mode { fn from_str(s: &str) -> Result { match s { "compile-fail" => Ok(CompileFail), - "parse-fail" => Ok(ParseFail), "run-fail" => Ok(RunFail), - "run-pass" => Ok(RunPass), "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), @@ -71,65 +65,135 @@ impl FromStr for Mode { "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 { - fmt::Display::fmt(match *self { - CompileFail => "compile-fail", - ParseFail => "parse-fail", - RunFail => "run-fail", - RunPass => "run-pass", - RunPassValgrind => "run-pass-valgrind", - Pretty => "pretty", - DebugInfoGdb => "debuginfo-gdb", - DebugInfoLldb => "debuginfo-lldb", - Codegen => "codegen", - Rustdoc => "rustdoc", - CodegenUnits => "codegen-units", - Incremental => "incremental", - RunMake => "run-make", - Ui => "ui", - MirOpt => "mir-opt", - }, - f) + 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, + RunFail, +} + +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", + PassMode::RunFail => "run-fail", + }; + 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 { - /// The library paths required for running the compiler + /// `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 + /// The library paths required for running compiled programs. pub run_lib_path: PathBuf, - /// The rustc executable + /// The rustc executable. pub rustc_path: PathBuf, - /// The rustdoc executable + /// The rustdoc executable. pub rustdoc_path: Option, - /// The python executable to use for LLDB + /// The Python executable to use for LLDB. pub lldb_python: String, - /// The python executable to use for htmldocck + /// The Python executable to use for htmldocck. pub docck_python: String, - /// The llvm FileCheck binary path + /// The LLVM `FileCheck` binary path. pub llvm_filecheck: Option, - /// The valgrind path + /// 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, @@ -139,7 +203,7 @@ pub struct Config { /// The name of the stage being built (stage1, etc) pub stage_id: String, - /// The test mode, compile-fail, run-fail, run-pass + /// The test mode, compile-fail, run-fail, ui pub mode: Mode, /// Run ignored tests @@ -151,6 +215,9 @@ pub struct Config { /// 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, @@ -170,6 +237,9 @@ pub struct Config { /// 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, @@ -182,6 +252,9 @@ pub struct Config { /// 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, @@ -215,6 +288,9 @@ pub struct Config { /// 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` @@ -229,19 +305,74 @@ pub struct Config { 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(Clone)] +#[derive(Debug, Clone)] pub struct TestPaths { pub file: PathBuf, // e.g., compile-test/foo/bar/baz.rs - pub base: PathBuf, // e.g., compile-test, auxiliary 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, UI_RUN_STDERR, UI_RUN_STDOUT]; pub const UI_STDERR: &str = "stderr"; pub const UI_STDOUT: &str = "stdout"; pub const UI_FIXED: &str = "fixed"; +pub const UI_RUN_STDERR: &str = "run.stderr"; +pub const UI_RUN_STDOUT: &str = "run.stdout"; + +/// 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)) +} + impl Config { /// Add rustc flags to link with the crate's dependencies in addition to the crate itself @@ -347,7 +478,7 @@ impl Default for Config { src_base: PathBuf::from("tests/run-pass"), build_base: env::temp_dir(), stage_id: "stage-id".to_owned(), - mode: Mode::RunPass, + mode: Mode::Ui, run_ignored: false, filter: None, filter_exact: false, @@ -387,6 +518,20 @@ impl Default for Config { llvm_components: "llvm-components".to_string(), llvm_cxxflags: "llvm-cxxflags".to_string(), nodejs: None, + bless: false, + cdb: None, + compare_mode: None, + force_pass_mode: None, + lldb_native_rust: false, + llvm_bin_dir: None, + run_clang_based_tests_with: None, } } } + +/// 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/errors.rs b/src/errors.rs index 251dd4d..5b3936f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,21 +1,14 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. use self::WhichLine::*; use std::fmt; use std::fs::File; -use std::io::BufReader; 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, @@ -35,15 +28,14 @@ impl FromStr for ErrorKind { "ERROR" => Ok(ErrorKind::Error), "NOTE" => Ok(ErrorKind::Note), "SUGGESTION" => Ok(ErrorKind::Suggestion), - "WARN" | - "WARNING" => Ok(ErrorKind::Warning), + "WARN" | "WARNING" => Ok(ErrorKind::Warning), _ => Err(()), } } } impl fmt::Display for ErrorKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { ErrorKind::Help => write!(f, "help message"), ErrorKind::Error => write!(f, "error"), @@ -57,7 +49,7 @@ impl fmt::Display for ErrorKind { #[derive(Debug)] pub struct Error { pub line_num: usize, - /// What kind of message we expect (e.g. warning, error, suggestion). + /// 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, @@ -101,31 +93,36 @@ pub fn load_errors(testfile: &Path, cfg: Option<&str>) -> Vec { rdr.lines() .enumerate() .filter_map(|(line_num, line)| { - parse_expected(last_nonfollow_error, line_num + 1, &line.unwrap(), &tag) - .map(|(which, error)| { + 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 = match line.find(tag) { - Some(i) => i, - None => return None, - }; +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()) + ( + false, + line[start + tag.len()..] + .chars() + .take_while(|c| *c == '^') + .count(), + ) }; let kind_start = start + tag.len() + adjusts + (follow as usize); let (kind, msg); @@ -133,12 +130,14 @@ fn parse_expected(last_nonfollow_error: Option, .split_whitespace() .next() .expect("Encountered unexpected empty comment") - .parse::() { + .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()) + msg = letters + .skip_while(|c| c.is_whitespace()) .skip_while(|c| !c.is_whitespace()) .collect::(); } @@ -146,7 +145,8 @@ fn parse_expected(last_nonfollow_error: Option, // Otherwise we found `//~ foo`: kind = None; let letters = line[kind_start..].chars(); - msg = letters.skip_while(|c| c.is_whitespace()) + msg = letters + .skip_while(|c| c.is_whitespace()) .collect::(); } } @@ -154,8 +154,10 @@ fn parse_expected(last_nonfollow_error: Option, 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."); + let line_num = last_nonfollow_error.expect( + "encountered //~| without \ + preceding //~^ line.", + ); (FollowPrevious(line_num), line_num) } else { let which = if adjusts > 0 { @@ -167,16 +169,16 @@ fn parse_expected(last_nonfollow_error: Option, (which, line_num) }; - debug!("line={} tag={:?} which={:?} kind={:?} msg={:?}", - line_num, - tag, - which, - kind, - msg); - Some((which, - Error { - line_num, - kind, - msg, - })) + debug!( + "line={} tag={:?} which={:?} kind={:?} msg={:?}", + line_num, tag, which, kind, msg + ); + Some(( + which, + Error { + line_num, + kind, + msg, + }, + )) } diff --git a/src/header.rs b/src/header.rs index 5b2a65c..d750595 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,65 +1,172 @@ -// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - use std::env; use std::fs::File; -use std::io::BufReader; use std::io::prelude::*; +use std::io::BufReader; use std::path::{Path, PathBuf}; -use common::Config; -use common; -use util; +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, + } + } +} -use extract_gdb_version; +/// 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: bool, + 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: false, + ignore: Ignore::Run, should_fail: false, aux: Vec::new(), + revisions: vec![], }; - iter_header(testfile, - None, - &mut |ln| { - props.ignore = - props.ignore || - config.parse_cfg_name_directive(ln, "ignore") || - ignore_gdb(config, ln) || - ignore_lldb(config, ln) || - ignore_llvm(config, ln); + 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.target == "wasm32-unknown-unknown" && config.parse_check_run_results(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 config.mode != common::DebugInfoGdb { - return false; - } - 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); @@ -116,10 +223,6 @@ impl EarlyProps { } fn ignore_lldb(config: &Config, line: &str) -> bool { - if config.mode != common::DebugInfoLldb { - return false; - } - if let Some(ref actual_version) = config.lldb_version { if line.starts_with("min-lldb-version") { let min_version = line.trim_end() @@ -129,6 +232,8 @@ impl EarlyProps { // 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 } @@ -139,7 +244,7 @@ impl EarlyProps { fn ignore_llvm(config: &Config, line: &str) -> bool { if config.system_llvm && line.starts_with("no-system-llvm") { - return true; + return true; } if let Some(ref actual_version) = config.llvm_version { if line.starts_with("min-llvm-version") { @@ -157,7 +262,30 @@ impl EarlyProps { .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) + 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 } @@ -183,8 +311,15 @@ pub struct TestProps { // 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 @@ -195,7 +330,17 @@ pub struct TestProps { pub force_host: bool, // Check stdout for error-pattern output as well as stderr pub check_stdout: bool, + // Check stdout & stderr for output of run-pass test + pub check_run_results: 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, @@ -212,18 +357,24 @@ pub struct TestProps { // testing harness and used when generating compilation // arguments. (In particular, it propagates to the aux-builds.) pub incremental_dir: Option, - // Specifies that a cfail test must actually compile without errors. - pub must_compile_successfully: bool, + // 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, - // The test must be compiled and run successfully. Only used in UI tests for - // now. - pub run_pass: 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 { @@ -234,34 +385,38 @@ impl TestProps { 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, + check_run_results: 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, - must_compile_successfully: false, + pass_mode: None, + ignore_pass: false, check_test_line_numbers_match: false, - run_pass: 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 { + 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: @@ -277,24 +432,19 @@ impl TestProps { props } - /// Load properties from `testfile` into `props`. If a property is + /// 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| { + 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())); + self.compile_flags + .extend(flags.split_whitespace().map(|s| s.to_owned())); } if let Some(edition) = config.parse_edition(ln) { @@ -325,6 +475,18 @@ impl TestProps { self.check_stdout = config.parse_check_stdout(ln); } + if !self.check_run_results { + self.check_run_results = config.parse_check_run_results(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); } @@ -345,6 +507,10 @@ impl TestProps { 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); } @@ -353,6 +519,10 @@ impl TestProps { 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); } @@ -361,16 +531,19 @@ impl TestProps { self.forbid_output.push(of); } - if !self.must_compile_successfully { - self.must_compile_successfully = config.parse_must_compile_successfully(ln); - } - if !self.check_test_line_numbers_match { self.check_test_line_numbers_match = config.parse_check_test_line_numbers_match(ln); } - if !self.run_pass { - self.run_pass = config.parse_run_pass(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") { @@ -380,6 +553,10 @@ impl TestProps { 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); } @@ -388,8 +565,19 @@ impl TestProps { 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() { @@ -398,12 +586,74 @@ impl TestProps { } } } + + 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 if config.parse_name_directive(ln, "run-fail") { + if config.mode != Mode::Ui { + panic!("`run-fail` header is only supported in UI tests") + } + Some(PassMode::RunFail) + } 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 + } + + // does not consider CLI override for pass mode + pub fn local_pass_mode(&self) -> Option { + 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 @@ -413,23 +663,24 @@ fn iter_header(testfile: &Path, cfg: Option<&str>, it: &mut dyn FnMut(&str)) { let ln = ln.trim(); if ln.starts_with("fn") || ln.starts_with("mod") { return; - } else if ln.starts_with("//[") { + } 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 lncfg = &ln[3..close_brace]; + 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()); + it(ln[(close_brace + 1)..].trim_start()); } } else { - panic!("malformed condition directive: expected `//[foo]`, found `{}`", - ln) + panic!("malformed condition directive: expected `{}foo]`, found `{}`", + comment_with_brace, ln) } - } else if ln.starts_with("//") { - it(ln[2..].trim_start()); + } else if ln.starts_with(comment) { + it(ln[comment.len() ..].trim_start()); } } return; @@ -446,6 +697,11 @@ impl Config { 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 { @@ -477,6 +733,18 @@ impl Config { self.parse_name_directive(line, "check-stdout") } + fn parse_check_run_results(&self, line: &str) -> bool { + self.parse_name_directive(line, "check-run-results") + } + + 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") } @@ -493,27 +761,37 @@ impl Config { self.parse_name_directive(line, "pretty-compare-only") } - fn parse_must_compile_successfully(&self, line: &str) -> bool { - self.parse_name_directive(line, "must-compile-successfully") + 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_run_pass(&self, line: &str) -> bool { - self.parse_name_directive(line, "run-pass") + 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(); + let mut strs: Vec = nv.splitn(2, '=').map(str::to_owned).collect(); match strs.len() { - 1 => (strs.pop().unwrap(), "".to_owned()), + 1 => (strs.pop().unwrap(), String::new()), 2 => { let end = strs.pop().unwrap(); (strs.pop().unwrap(), end) @@ -534,58 +812,109 @@ impl Config { } fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(String, String)> { - if self.parse_cfg_name_directive(line, prefix) { - let from = match parse_normalization_string(&mut line) { - Some(s) => s, - None => return None, - }; - let to = match parse_normalization_string(&mut line) { - Some(s) => s, - None => return None, - }; + 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 } } - /// Parses a name-value directive which contains config-specific information, e.g. `ignore-x86` - /// or `normalize-stderr-32bit`. Returns `true` if the line matches it. - fn parse_cfg_name_directive(&self, line: &str, prefix: &str) -> bool { + 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(); + let name = line[prefix.len() + 1..] + .split(&[':', ' '][..]) + .next() + .unwrap(); - name == "test" || + if name == "test" || util::matches_os(&self.target, name) || // target + util::matches_env(&self.target, name) || // env 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 - match self.mode { - common::DebugInfoGdb => name == "gdb", - common::DebugInfoLldb => name == "lldb", - common::Pretty => name == "pretty", - _ => false, + (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, } || - (self.target != self.host && name == "cross-compile") + (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 { - false + 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 + _ => 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(); + let value = line[(colon + 1)..].to_owned(); debug!("{}: {}", directive, value); Some(expand_variables(value, self)) } else { @@ -620,8 +949,10 @@ impl Config { } pub fn lldb_version_to_int(version_string: &str) -> isize { - let error_string = format!("Encountered LLDB version string with unexpected format: {}", - version_string); + let error_string = format!( + "Encountered LLDB version string with unexpected format: {}", + version_string + ); version_string.parse().expect(&error_string) } @@ -651,7 +982,7 @@ fn expand_variables(mut value: String, config: &Config) -> String { /// /// # Examples /// -/// ```ignore +/// ``` /// 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())); @@ -659,15 +990,9 @@ fn expand_variables(mut value: String, config: &Config) -> String { /// ``` fn parse_normalization_string(line: &mut &str) -> Option { // FIXME support escapes in strings. - let begin = match line.find('"') { - Some(i) => i + 1, - None => return None, - }; - let end = match line[begin..].find('"') { - Some(i) => i + begin, - None => return None, - }; + let begin = line.find('"')? + 1; + let end = line[begin..].find('"')? + begin; let result = line[begin..end].to_owned(); - *line = &line[end+1..]; + *line = &line[end + 1..]; Some(result) } diff --git a/src/header/tests.rs b/src/header/tests.rs new file mode 100644 index 0000000..2a1831d --- /dev/null +++ b/src/header/tests.rs @@ -0,0 +1,27 @@ +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/json.rs b/src/json.rs index 6f9e2ff..7930d12 100644 --- a/src/json.rs +++ b/src/json.rs @@ -1,21 +1,12 @@ -// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. +//! These structs are a subset of the ones found in `rustc_errors::json`. +//! They are only used for deserialization of JSON output provided by libtest. -use errors::{Error, ErrorKind}; +use crate::errors::{Error, ErrorKind}; +use crate::runtest::ProcRes; +use serde::Deserialize; use serde_json; +use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::path::Path; -use runtest::ProcRes; - -// These structs are a subset of the ones found in -// `syntax::json`. #[derive(Deserialize)] struct Diagnostic { @@ -27,6 +18,12 @@ struct Diagnostic { rendered: Option, } +#[derive(Deserialize)] +struct ArtifactNotification { + #[allow(dead_code)] + artifact: PathBuf, +} + #[derive(Deserialize, Clone)] struct DiagnosticSpan { file_name: String, @@ -72,20 +69,22 @@ struct DiagnosticCode { explanation: Option, } -pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String { +pub fn extract_rendered(output: &str) -> String { output .lines() .filter_map(|line| { if line.starts_with('{') { - match serde_json::from_str::(line) { - Ok(diagnostic) => diagnostic.rendered, - Err(error) => { - proc_res.fatal(Some(&format!( - "failed to decode compiler output as json: \ - `{}`\nline: {}\noutput: {}", - error, line, output - ))); - } + 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 @@ -96,7 +95,8 @@ pub fn extract_rendered(output: &str, proc_res: &ProcRes) -> String { } pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec { - output.lines() + output + .lines() .flat_map(|line| parse_line(file_name, line, output, proc_res)) .collect() } @@ -112,11 +112,11 @@ fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> expected_errors } Err(error) => { - proc_res.fatal(Some(&format!("failed to decode compiler output as json: \ - `{}`\noutput: {}\nline: {}", - error, - line, - output))); + proc_res.fatal(Some(&format!( + "failed to decode compiler output as json: \ + `{}`\nline: {}\noutput: {}", + error, line, output + ))); } } } else { @@ -124,10 +124,12 @@ fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> } } -fn push_expected_errors(expected_errors: &mut Vec, - diagnostic: &Diagnostic, - default_spans: &[&DiagnosticSpan], - file_name: &str) { +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 @@ -231,8 +233,10 @@ fn push_expected_errors(expected_errors: &mut Vec, } // Add notes for any labels that appear in the message. - for span in spans_in_this_file.iter() - .filter(|span| span.label.is_some()) { + 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), @@ -246,9 +250,11 @@ fn push_expected_errors(expected_errors: &mut Vec, } } -fn push_backtrace(expected_errors: &mut Vec, - expansion: &DiagnosticSpanMacroExpansion, - file_name: &str) { +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, diff --git a/src/lib.rs b/src/lib.rs index c20ebc8..6706a88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,23 +33,28 @@ extern crate regex; extern crate filetime; extern crate diff; extern crate serde_json; -#[macro_use] extern crate serde_derive; extern crate rustfix; use std::env; use std::ffi::OsString; use std::fs; -use std::io; +use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; -use common::{Mode, TestPaths}; -use common::{Pretty, DebugInfoGdb, DebugInfoLldb}; +use std::process::Command; +use std::time::SystemTime; +use walkdir::WalkDir; +use crate::common::{expected_output_path, output_base_dir, output_relative_path, UI_EXTENSIONS}; +use crate::common::{TestPaths}; +use crate::common::{DebugInfoCdb, DebugInfoGdbLldb, DebugInfoGdb, DebugInfoLldb, Mode, Pretty}; +use crate::util::logv; -use self::header::EarlyProps; +use self::header::{EarlyProps, Ignore}; pub mod uidiff; pub mod util; mod json; +mod raise_fd_limit; pub mod header; pub mod runtest; pub mod common; @@ -58,24 +63,136 @@ mod read2; pub use common::Config; +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 let DebugInfoGdb = config.mode { - println!("{} debug-info test uses tcp 5039 port.\ - please reserve it", config.target); + 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; + } } - // android debug-info test uses remote debugger - // so, we test 1 thread at once. - // also trying to isolate problems with adb_run_wrapper.sh ilooping - env::set_var("RUST_TEST_THREADS","1"); + DebugInfoCdb | _ => { /* proceed */ } } - if let DebugInfoLldb = config.mode { - // Some older versions of LLDB seem to have problems with multiple - // instances running in parallel, so only run one test task at a - // time. - env::set_var("RUST_TEST_TASKS", "1"); + // 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, @@ -90,16 +207,23 @@ pub fn run_tests(config: &Config) { } } } + let opts = test_opts(config); let tests = make_tests(config); // sadly osx needs some file descriptor limits raised for running tests in // parallel (especially when we have lots and lots of child processes). // For context, see #8904 - // unsafe { raise_fd_limit::raise_fd_limit(); } + 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 res = test::run_tests_console(&opts, tests.into_iter().collect()); + + // 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"), @@ -111,64 +235,105 @@ pub fn run_tests(config: &Config) { pub fn test_opts(config: &Config) -> test::TestOpts { test::TestOpts { + exclude_should_panic: false, filter: config.filter.clone(), filter_exact: config.filter_exact, - exclude_should_panic: false, - force_run_in_process: false, - run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No }, - format: if config.quiet { test::OutputFormat::Terse } else { test::OutputFormat::Pretty }, + 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 + Err(_) => false, }, - color: test::AutoColor, + color: config.color, test_threads: None, skip: vec![], list: false, options: test::Options::new(), time_options: None, + force_run_in_process: false, } } pub fn make_tests(config: &Config) -> Vec { - debug!("making tests from {:?}", - config.src_base.display()); + debug!("making tests from {:?}", config.src_base.display()); + let inputs = common_inputs_stamp(config); let mut tests = Vec::new(); - collect_tests_from_dir(config, - &config.src_base, - &config.src_base, - &PathBuf::new(), - &mut tests) - .unwrap(); + collect_tests_from_dir( + config, + &config.src_base, + &config.src_base, + &PathBuf::new(), + &inputs, + &mut tests, + ).expect(&format!("Could not read tests from {}", config.src_base.display())); 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 - // `compiletest-ignore-dir`. - for file in fs::read_dir(dir)? { - let file = file?; - let name = file.file_name(); - if name == *"compiletest-ignore-dir" { - return Ok(()); - } - if name == *"Makefile" && config.mode == Mode::RunMake { - let paths = TestPaths { - file: dir.to_path_buf(), - base: base.to_path_buf(), - relative_dir: relative_dir_path.parent().unwrap().to_path_buf(), - }; - tests.push(make_test(config, &paths)); - return Ok(()) - } +/// Returns a stamp constructed from input files common to all test cases. +fn common_inputs_stamp(config: &Config) -> Stamp { + let rust_src_dir = config + .find_rust_src_root() + .expect("Could not find Rust source root"); + + let mut stamp = Stamp::from_path(&config.rustc_path); + + // 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", + ]; + for file in &pretty_printer_files { + let path = rust_src_dir.join(file); + stamp.add_path(&path); + } + + stamp.add_dir(&config.run_lib_path); + + if let Some(ref rustdoc_path) = config.rustdoc_path { + stamp.add_path(&rustdoc_path); + stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py")); + } + + // Compiletest itself. + stamp.add_dir(&rust_src_dir.join("src/tools/compiletest/")); + + stamp +} + +fn collect_tests_from_dir( + config: &Config, + base: &Path, + dir: &Path, + relative_dir_path: &Path, + inputs: &Stamp, + 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, inputs)); + return Ok(()); } // If we find a test foo/bar.rs, we have to build the @@ -177,49 +342,29 @@ fn collect_tests_from_dir(config: &Config, // 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 = config.build_base.join(&relative_dir_path); + 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. - let dirs = fs::read_dir(dir)?; - for file in dirs { + 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()); - // 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 = config.build_base.join(&relative_dir_path); - fs::create_dir_all(&build_dir).unwrap(); - let paths = TestPaths { file: file_path, - base: base.to_path_buf(), relative_dir: relative_dir_path.to_path_buf(), }; - tests.push(make_test(config, &paths)) + tests.extend(make_test(config, &paths, inputs)) } else if file_path.is_dir() { let relative_file_path = relative_dir_path.join(file.file_name()); - if &file_name == "auxiliary" { - // `aux` directories contain other crates used for - // cross-crate tests. Don't search them for tests, but - // do create a directory in the build dir for them, - // since we will dump intermediate output in there - // sometimes. - let build_dir = config.build_base.join(&relative_file_path); - fs::create_dir_all(&build_dir).unwrap(); - } else { + if &file_name != "auxiliary" { debug!("found directory: {:?}", file_path.display()); - collect_tests_from_dir(config, - base, - &file_path, - &relative_file_path, - tests)?; + collect_tests_from_dir( + config, base, &file_path, &relative_file_path, + inputs, tests)?; } } else { debug!("found other file/directory: {:?}", file_path.display()); @@ -228,6 +373,8 @@ fn collect_tests_from_dir(config: &Config, 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(); @@ -240,8 +387,13 @@ pub fn is_test(file_name: &OsString) -> bool { !invalid_prefixes.iter().any(|p| file_name.starts_with(p)) } -pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn { - let early_props = EarlyProps::from_file(config, &testpaths.file); +fn make_test(config: &Config, testpaths: &TestPaths, inputs: &Stamp) -> 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. @@ -252,51 +404,268 @@ pub fn make_test(config: &Config, testpaths: &TestPaths) -> test::TestDescAndFn test::ShouldPanic::Yes } else { test::ShouldPanic::No - } + }, }; - test::TestDescAndFn { - desc: test::TestDesc { - name: make_test_name(config, testpaths), - ignore: early_props.ignore, - should_panic: should_panic, - allow_fail: false, - test_type: test::TestType::IntegrationTest, - }, - testfn: make_test_closure(config, testpaths), + // 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| { + let ignore = early_props.ignore == Ignore::Ignore + // Debugging emscripten code doesn't make sense today + || ((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()) + // Ignore tests that already run and are up to date with respect to inputs. + || is_up_to_date( + config, + testpaths, + &early_props, + revision.map(|s| s.as_str()), + inputs, + ); + test::TestDescAndFn { + desc: test::TestDesc { + name: make_test_name(config, testpaths, revision), + ignore, + should_panic, + allow_fail: false, + test_type: test::TestType::Unknown, + }, + 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 is_up_to_date( + config: &Config, + testpaths: &TestPaths, + props: &EarlyProps, + revision: Option<&str>, + inputs: &Stamp, +) -> 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 false, + }; + let expected_hash = runtest::compute_stamp_hash(config); + if contents != expected_hash { + return false; + } + + // Check timestamps. + let mut inputs = inputs.clone(); + inputs.add_path(&testpaths.file); + + for aux in &props.aux { + let path = testpaths.file.parent() + .unwrap() + .join("auxiliary") + .join(aux); + inputs.add_path(&path); + } + + // UI test files. + for extension in UI_EXTENSIONS { + let path = &expected_output_path(testpaths, revision, &config.compare_mode, extension); + inputs.add_path(path); } + + inputs < Stamp::from_path(&stamp_name) } -fn stamp(config: &Config, testpaths: &TestPaths) -> PathBuf { - let stamp_name = format!("{}-{}.stamp", - testpaths.file.file_name().unwrap() - .to_str().unwrap(), - config.stage_id); - config.build_base.canonicalize() - .unwrap_or_else(|_| config.build_base.clone()) - .join(stamp_name) +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Stamp { + time: SystemTime, } -pub fn make_test_name(config: &Config, testpaths: &TestPaths) -> test::TestName { +impl Stamp { + fn from_path(path: &Path) -> Self { + let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH }; + stamp.add_path(path); + stamp + } + + fn add_path(&mut self, path: &Path) { + let modified = fs::metadata(path) + .and_then(|metadata| metadata.modified()) + .unwrap_or(SystemTime::UNIX_EPOCH); + self.time = self.time.max(modified); + } + + fn add_dir(&mut self, path: &Path) { + for entry in WalkDir::new(path) { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let modified = entry.metadata().ok() + .and_then(|metadata| metadata.modified().ok()) + .unwrap_or(SystemTime::UNIX_EPOCH); + self.time = self.time.max(modified); + } + } + } +} + +fn make_test_name( + config: &Config, + testpaths: &TestPaths, + revision: Option<&String>, +) -> test::TestName { // Convert a complete path to something like // - // run-pass/foo/bar/baz.rs - let path = - PathBuf::from(config.src_base.file_name().unwrap()) + // 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()); - test::DynTestName(format!("[{}] {}", config.mode, path.display())) + 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)) + )) } -pub fn make_test_closure(config: &Config, testpaths: &TestPaths) -> test::TestFn { - let config = config.clone(); +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 || { - let config = config.clone(); // FIXME: why is this needed? - runtest::run(config, &testpaths) + 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(); @@ -311,7 +680,7 @@ fn extract_gdb_version(full_version_line: &str) -> Option { 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 + continue; } prev_was_digit = true; @@ -334,10 +703,15 @@ fn extract_gdb_version(full_version_line: &str) -> Option { 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_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) }; + let patch = if patch_len > 3 || patch_len == 0 { + None + } else { + Some(patch) + }; (&line[..idx], patch) } else { @@ -360,48 +734,79 @@ fn extract_gdb_version(full_version_line: &str) -> Option { None } -#[allow(dead_code)] -fn extract_lldb_version(full_version_line: Option) -> Option { +/// 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. - // At the moment, this function only supports the Apple variant, which looks - // like this: + // 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 } + 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 } + if l != 'l' && l != 'L' { + continue; + } let d = full_version_line[pos + 2..].chars().next().unwrap(); - if d != 'd' && d != 'D' { continue } + if d != 'd' && d != 'D' { + continue; + } let b = full_version_line[pos + 3..].chars().next().unwrap(); - if b != 'b' && b != 'B' { continue } + if b != 'b' && b != 'B' { + continue; + } let dash = full_version_line[pos + 4..].chars().next().unwrap(); - if dash != '-' { continue } + 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")); + } + } - let vers = full_version_line[pos + 5..].chars().take_while(|c| { - c.is_digit(10) - }).collect::(); - if !vers.is_empty() { return Some(vers) } + 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")); + } } - println!("Could not extract LLDB version from line '{}'", - full_version_line); } } - None + (None, false) } -#[allow(dead_code)] fn is_blacklisted_lldb_version(version: &str) -> bool { version == "350" } diff --git a/src/raise_fd_limit.rs b/src/raise_fd_limit.rs index fcc5a72..e9c9109 100644 --- a/src/raise_fd_limit.rs +++ b/src/raise_fd_limit.rs @@ -1,13 +1,3 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - /// 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 @@ -17,7 +7,6 @@ #[cfg(any(target_os = "macos", target_os = "ios"))] #[allow(non_camel_case_types)] pub unsafe fn raise_fd_limit() { - use libc; use std::cmp; use std::io; use std::mem::size_of_val; @@ -34,12 +23,15 @@ pub unsafe fn raise_fd_limit() { 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 { + 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); } @@ -54,14 +46,16 @@ pub unsafe fn raise_fd_limit() { panic!("raise_fd_limit: error calling getrlimit: {}", err); } - // 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); + // 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); + // 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); + } } } diff --git a/src/read2.rs b/src/read2.rs index 6645725..6dfd8e9 100644 --- a/src/read2.rs +++ b/src/read2.rs @@ -1,13 +1,3 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - // 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. @@ -16,11 +6,13 @@ pub use self::imp::read2; #[cfg(not(any(unix, windows)))] mod imp { use std::io::{self, Read}; - use std::process::{ChildStdout, ChildStderr}; + use std::process::{ChildStderr, ChildStdout}; - pub fn read2(out_pipe: ChildStdout, - err_pipe: ChildStderr, - data: &mut FnMut(bool, &mut Vec, bool)) -> io::Result<()> { + 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); @@ -33,16 +25,18 @@ mod imp { #[cfg(unix)] mod imp { - use std::io::prelude::*; + use libc; use std::io; + use std::io::prelude::*; use std::mem; use std::os::unix::prelude::*; - use std::process::{ChildStdout, ChildStderr}; - use libc; + 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<()> { + 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); @@ -58,15 +52,18 @@ mod imp { fds[0].events = libc::POLLIN; fds[1].fd = err_pipe.as_raw_fd(); fds[1].events = libc::POLLIN; - loop { + 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(), 2, -1) }; + 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 + continue; } - return Err(err) + return Err(err); } // Read as much as we can from each pipe, ignoring EWOULDBLOCK or @@ -74,48 +71,44 @@ mod imp { // 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) - } + let handle = |res: io::Result<_>| match res { + Ok(_) => Ok(true), + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock { + Ok(false) + } else { + Err(e) } } }; - if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? { - out_done = true; - } - data(true, &mut out, out_done); - if !err_done && fds[1].revents != 0 && handle(err_pipe.read_to_end(&mut err))? { + 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 && err_done { - return Ok(()) + 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 { - extern crate miow; - extern crate winapi; - use std::io; use std::os::windows::prelude::*; - use std::process::{ChildStdout, ChildStderr}; + use std::process::{ChildStderr, ChildStdout}; use std::slice; - use self::miow::iocp::{CompletionPort, CompletionStatus}; - use self::miow::pipe::NamedPipe; - use self::miow::Overlapped; - use self::winapi::shared::winerror::ERROR_BROKEN_PIPE; + 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, @@ -124,9 +117,11 @@ mod imp { done: bool, } - pub fn read2(out_pipe: ChildStdout, - err_pipe: ChildStderr, - data: &mut FnMut(bool, &mut Vec, bool)) -> io::Result<()> { + 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(); @@ -202,7 +197,9 @@ mod imp { 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()) + slice::from_raw_parts_mut( + v.as_mut_ptr().offset(v.len() as isize), + v.capacity() - v.len(), + ) } } diff --git a/src/runtest.rs b/src/runtest.rs index a2cc155..03e60c3 100644 --- a/src/runtest.rs +++ b/src/runtest.rs @@ -1,40 +1,74 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use common::{Config, TestPaths}; -use common::{UI_FIXED, UI_STDERR, UI_STDOUT}; -use common::{CompileFail, ParseFail, Pretty, RunFail, RunPass, RunPassValgrind}; -use common::{Codegen, DebugInfoLldb, DebugInfoGdb, Rustdoc, CodegenUnits}; -use common::{Incremental, RunMake, Ui, MirOpt}; +// 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::{UI_RUN_STDERR, UI_RUN_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 errors::{self, ErrorKind, Error}; -use filetime::FileTime; -use json; -use regex::Regex; +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 header::TestProps; -use util::logv; +use crate::util::{logv, PathBufExt}; -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::hash_map::DefaultHasher; +use std::collections::{HashMap, HashSet, VecDeque}; use std::env; -use std::ffi::OsString; -use std::fs::{self, File, create_dir_all, OpenOptions}; +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::{Command, Output, ExitStatus, Stdio, Child}; +use std::process::{Child, Command, ExitStatus, Output, Stdio}; use std::str; -use extract_gdb_version; +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 { @@ -49,10 +83,113 @@ pub fn dylib_env_var() -> &'static str { } } -pub fn run(config: Config, testpaths: &TestPaths) { - match &*config.target { +/// 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); - "arm-linux-androideabi" | "armv7-linux-androideabi" | "aarch64-linux-android" => { + 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"); } @@ -71,41 +208,74 @@ pub fn run(config: Config, testpaths: &TestPaths) { print!("\n\n"); } debug!("running {:?}", testpaths.file.display()); - let base_props = TestProps::from_file(&testpaths.file, None, &config); - - let base_cx = TestCx { config: &config, - props: &base_props, - testpaths, - revision: None }; - base_cx.init_all(); - - if base_props.revisions.is_empty() { - base_cx.run_revision() - } else { - for revision in &base_props.revisions { - let revision_props = TestProps::from_file(&testpaths.file, - Some(revision), - &config); + 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) + revision: Some(revision), }; rev_cx.run_revision(); } + } else { + cx.run_revision(); } - base_cx.complete_all(); + 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); + } - File::create(::stamp(&config, testpaths)).unwrap(); + 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> + revision: Option<&'test str>, } struct DebuggerCommands { @@ -114,25 +284,30 @@ struct DebuggerCommands { breakpoint_lines: Vec, } -impl<'test> TestCx<'test> { - /// invoked once before any revisions have been processed - fn init_all(&self) { - assert!(self.revision.is_none(), "init_all invoked for a revision"); - if let Incremental = self.config.mode { - self.init_incremental_test() - } - } +enum ReadFrom { + Path, + Stdin(String), +} +enum TestOutput { + Compile, + Run, +} + +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 | - ParseFail => self.run_cfail_test(), + CompileFail => self.run_cfail_test(), RunFail => self.run_rfail_test(), - RunPass => self.run_rpass_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(), @@ -142,32 +317,73 @@ impl<'test> TestCx<'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(), } } - /// Invoked after all revisions have executed. - fn complete_all(&self) { - assert!(self.revision.is_none(), "init_all invoked for a revision"); + fn pass_mode(&self) -> Option { + self.props.pass_mode(self.config) } - fn run_cfail_test(&self) { - let proc_res = self.compile_test(); + fn should_run(&self) -> bool { + let pass_mode = self.pass_mode(); + match self.config.mode { + Ui => pass_mode == Some(PassMode::Run) || pass_mode == Some(PassMode::RunFail), + mode => panic!("unimplemented for mode {:?}", mode), + } + } - if self.props.must_compile_successfully { + 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); + 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); + proc_res, + ); } - self.check_correct_failure_status(&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); @@ -180,7 +396,6 @@ impl<'test> TestCx<'test> { self.check_error_patterns(&output_to_check, &proc_res); } - self.check_no_compiler_crash(&proc_res); self.check_forbid_output(&output_to_check, &proc_res); } @@ -213,13 +428,17 @@ impl<'test> TestCx<'test> { } fn check_correct_failure_status(&self, proc_res: &ProcRes) { - // The value the rust runtime returns on failure - const RUST_ERR: i32 = 1; - if proc_res.status.code() != Some(RUST_ERR) { + 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!("failure produced the wrong error: {}", - proc_res.status), - proc_res); + &format!( + "Error: expected failure status ({:?}) but received status {:?}.", + expected_status, received_status + ), + proc_res, + ); } } @@ -232,11 +451,12 @@ impl<'test> TestCx<'test> { // 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/"); + 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); } @@ -258,7 +478,10 @@ impl<'test> TestCx<'test> { let mut new_config = self.config.clone(); new_config.runtool = new_config.valgrind_path.clone(); - let new_cx = TestCx { config: &new_config, ..*self }; + let new_cx = TestCx { + config: &new_config, + ..*self + }; proc_res = new_cx.exec_compiled_test(); if !proc_res.status.success() { @@ -270,28 +493,48 @@ impl<'test> TestCx<'test> { 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()); + logv( + self.config, + "testing for converging pretty-printing".to_owned(), + ); } - let rounds = match self.props.pp_exact { Some(_) => 1, None => 2 }; + let rounds = match self.props.pp_exact { + Some(_) => 1, + None => 2, + }; - let mut src = String::new(); - File::open(&self.testpaths.file).unwrap().read_to_string(&mut src).unwrap(); + 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 proc_res = self.print_source(srcs[round].to_owned(), &self.props.pretty_mode); + 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); + self.fatal_proc_rec( + &format!( + "pretty-printing failed in round {} revision {:?}", + round, self.revision + ), + &proc_res, + ); } - let ProcRes{ stdout, .. } = proc_res; + let ProcRes { stdout, .. } = proc_res; srcs.push(stdout); round += 1; } @@ -299,11 +542,9 @@ impl<'test> TestCx<'test> { let mut expected = match self.props.pp_exact { Some(ref file) => { let filepath = self.testpaths.file.parent().unwrap().join(file); - let mut s = String::new(); - File::open(&filepath).unwrap().read_to_string(&mut s).unwrap(); - s + fs::read_to_string(&filepath).unwrap() } - None => { srcs[srcs.len() - 2].clone() } + None => srcs[srcs.len() - 2].clone(), }; let mut actual = srcs[srcs.len() - 1].clone(); @@ -317,7 +558,9 @@ impl<'test> TestCx<'test> { 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; } + 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); @@ -325,58 +568,85 @@ impl<'test> TestCx<'test> { self.fatal_proc_rec("pretty-printed source does not typecheck", &proc_res); } - if !self.props.pretty_expanded { return } + if !self.props.pretty_expanded { + return; + } // additionally, run `--pretty expanded` and try to build it. - let proc_res = self.print_source(srcs[round].clone(), "expanded"); + 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 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); + &proc_res, + ); } } - fn print_source(&self, src: String, pretty_type: &str) -> ProcRes { + 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("-") + rustc + .arg(input) .args(&["-Z", &format!("unpretty={}", pretty_type)]) .args(&["--target", &self.config.target]) - .arg("-L").arg(&aux_dir) - .args(self.split_maybe_args(&self.config.target_rustcflags)) + .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()), - Some(src)) + 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) { + fn compare_source(&self, expected: &str, actual: &str) { if expected != actual { - self.error("pretty-printed source does not match expected source"); - println!("\n\ -expected:\n\ -------------------------------------------\n\ -{}\n\ -------------------------------------------\n\ -actual:\n\ -------------------------------------------\n\ -{}\n\ -------------------------------------------\n\ -\n", - expected, actual); - panic!(); + 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]); } } @@ -395,21 +665,111 @@ actual:\n\ let aux_dir = self.aux_output_dir_name(); - rustc.arg("-") - .arg("-Zno-trans") - .arg("--out-dir").arg(&out_dir) + 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); + .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); - if let Some(revision) = self.revision { - rustc.args(&["--cfg", revision]); + 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); } - rustc.args(self.split_maybe_args(&self.config.target_rustcflags)); - rustc.args(&self.props.compile_flags); + let exe_file = self.make_exe_name(); - self.compose_and_run_compiler(rustc, Some(src)) + 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) { @@ -418,7 +778,8 @@ actual:\n\ 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), - .. self.config.clone() + mode: DebugInfoGdb, + ..self.config.clone() }; let test_cx = TestCx { @@ -445,7 +806,7 @@ actual:\n\ let DebuggerCommands { commands, check_lines, - breakpoint_lines + breakpoint_lines, } = self.parse_debugger_commands(prefixes); let mut cmds = commands.join("\n"); @@ -458,207 +819,217 @@ actual:\n\ let exe_file = self.make_exe_name(); let debugger_run_result; - match &*self.config.target { - "arm-linux-androideabi" | - "armv7-linux-androideabi" | - "aarch64-linux-android" => { + if is_android_gdb_target(&self.config.target) { + cmds = cmds.replace("run", "continue"); - 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"), + }; - 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"); - // 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 debugger_script = self.make_out_name("debugger.script"); - // FIXME (#9639): This needs to handle non-utf8 paths - let debugger_opts = - vec!["-quiet".to_owned(), - "-batch".to_owned(), - "-nx".to_owned(), - format!("-command={}", debugger_script.to_str().unwrap())]; - - let mut gdb_path = tool_path; - gdb_path.push_str("/bin/gdb"); - 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 - }; + debug!("script_str = {}", script_str); + self.dump_output_file(&script_str, "debugger.script"); - 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."); + 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 + }; - _ => { - 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"); + 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"); + // 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)); + // 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"\\"))); + // 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"); - } + // 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)); - } + // 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"); + script_str.push_str(&cmds); + script_str.push_str("\nquit\n"); - debug!("script_str = {}", script_str); - self.dump_output_file(&script_str, "debugger.script"); + debug!("script_str = {}", script_str); + self.dump_output_file(&script_str, "debugger.script"); - let debugger_script = self.make_out_name("debugger.script"); + let mut debugger_script = OsString::from("-command="); + debugger_script.push(self.make_out_name("debugger.script")); - // FIXME (#9639): This needs to handle non-utf8 paths - let debugger_opts = - vec!["-quiet".to_owned(), - "-batch".to_owned(), - "-nx".to_owned(), - format!("-command={}", debugger_script.to_str().unwrap())]; + let 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); + 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); - } + debugger_run_result = self.compose_and_run( + gdb, + self.config.run_lib_path.to_str().unwrap(), + None, + None, + ); } if !debugger_run_result.status.success() { @@ -678,10 +1049,10 @@ actual:\n\ 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), - .. self.config.clone() + mode: DebugInfoLldb, + ..self.config.clone() }; - let test_cx = TestCx { config: &config, ..*self @@ -701,22 +1072,36 @@ actual:\n\ match self.config.lldb_version { Some(ref version) => { - println!("NOTE: compiletest thinks it is using LLDB version {}", - version); + println!( + "NOTE: compiletest thinks it is using LLDB version {}", + version + ); } _ => { - println!("NOTE: compiletest does not know which version of \ - LLDB it is using"); + 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(&["lldb"]); + } = self.parse_debugger_commands(prefixes); // Write debugger script: // We don't want to hang when calling `quit` while the process is still running @@ -726,17 +1111,19 @@ actual:\n\ 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_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[..])[..]); + 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"); @@ -745,9 +1132,10 @@ actual:\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)); + script_str.push_str(&format!( + "breakpoint set --file '{}' --line {}\n", + source_file_name, line + )); } // Append the other commands @@ -765,9 +1153,7 @@ actual:\n\ 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); + 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); @@ -776,32 +1162,39 @@ actual:\n\ self.check_debugger_output(&debugger_run_result, &check_lines); } - fn run_lldb(&self, - test_executable: &Path, - debugger_script: &Path, - rust_src_root: &Path) - -> ProcRes { + 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())) + 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)) - } + 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); @@ -809,15 +1202,15 @@ actual:\n\ status, stdout: out, stderr: err, - cmdline: format!("{:?}", cmd) + 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 directives = debugger_prefixes + .iter() + .map(|prefix| (format!("{}-command", prefix), format!("{}-check", prefix))) + .collect::>(); let mut breakpoint_lines = vec![]; let mut commands = vec![]; @@ -827,27 +1220,27 @@ actual:\n\ 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) - }); + 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)) - } + Err(e) => self.fatal(&format!("Error while parsing debugger commands: {}", e)), } counter += 1; } @@ -865,19 +1258,45 @@ actual:\n\ } // 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::>(); + 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(); @@ -892,9 +1311,13 @@ actual:\n\ } } 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); + 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 { @@ -905,17 +1328,18 @@ actual:\n\ 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(); + 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 + Some(pos) => (&line[pos + check_fragments[0].len()..], 1), + None => return false, } } else { (line, 0) @@ -924,9 +1348,9 @@ actual:\n\ for current_fragment in &check_fragments[first_fragment..] { match rest.find(current_fragment) { Some(pos) => { - rest = &rest[pos + current_fragment.len() .. ]; + rest = &rest[pos + current_fragment.len()..]; } - None => return false + None => return false, } } @@ -938,58 +1362,55 @@ actual:\n\ } } - fn check_error_patterns(&self, - output_to_check: &str, - proc_res: &ProcRes) { + 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.props.must_compile_successfully { - return + if self.pass_mode().is_some() { + return; } else { - self.fatal(&format!("no error pattern specified in {:?}", - self.testpaths.file.display())); - } - } - let mut next_err_idx = 0; - let mut next_err_pat = self.props.error_patterns[next_err_idx].trim(); - let mut done = false; - for line in output_to_check.lines() { - if line.contains(next_err_pat) { - debug!("found error pattern {}", next_err_pat); - next_err_idx += 1; - if next_err_idx == self.props.error_patterns.len() { - debug!("found all error patterns"); - done = true; - break; - } - next_err_pat = self.props.error_patterns[next_err_idx].trim(); + 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 done { return; } - let missing_patterns = &self.props.error_patterns[next_err_idx..]; + 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); + proc_res, + ); } else { for pattern in missing_patterns { - self.error(&format!("error pattern '{}' not found!", *pattern)); + 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) { - for line in proc_res.stderr.lines() { - if line.contains("error: internal compiler error") { - self.fatal_proc_rec("compiler encountered internal error", proc_res); - } + 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) { + 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); @@ -997,11 +1418,14 @@ actual:\n\ } } - fn check_expected_errors(&self, - expected_errors: Vec, - proc_res: &ProcRes) { - if proc_res.status.success() && - expected_errors.iter().any(|x| x.kind == Some(ErrorKind::Error)) { + 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); } @@ -1009,33 +1433,33 @@ actual:\n\ // from the compiler let os_file_name = self.testpaths.file.display().to_string(); - let file_name = - format!("{}", self.testpaths.file.display()) - .replace(r"\", "/"); // on windows, translate all '\' path separators to '/' + // 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)); + 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) - }); + 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) => { @@ -1046,14 +1470,16 @@ actual:\n\ 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)); + 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); } } @@ -1064,24 +1490,27 @@ actual:\n\ // 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)); + 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); + 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); } @@ -1092,28 +1521,40 @@ actual:\n\ } } - /// Returns true if we should report an error about `actual_error`, + /// 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 { + 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 + Some(ErrorKind::Error) | Some(ErrorKind::Warning) => true, + Some(ErrorKind::Suggestion) | None => false, } } fn compile_test(&self) -> ProcRes { - let mut rustc = self.make_compile_args( - &self.testpaths.file, TargetLocation::ThisFile(self.make_exe_name())); + // Only use `make_exe_name` when the test ends up being executed. + let will_execute = match self.config.mode { + Ui => self.should_run(), + 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()); @@ -1124,7 +1565,13 @@ actual:\n\ // 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. - rustc.args(&["-A", "unused"]); + if !self.config.src_base.ends_with("rustdoc-ui") && + // Note that we don't call pass_mode() here as we don't want + // to set unused to allow if we've overriden the pass mode + // via command line flags. + self.props.local_pass_mode() != Some(PassMode::Run) { + rustc.args(&["-A", "unused"]); + } } _ => {} } @@ -1136,15 +1583,17 @@ actual:\n\ 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_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 + 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; @@ -1154,15 +1603,25 @@ actual:\n\ let aux_dir = self.aux_output_dir_name(); - let rustdoc_path = self.config.rustdoc_path.as_ref().expect("--rustdoc-path passed"); + let rustdoc_path = self + .config + .rustdoc_path + .as_ref() + .expect("--rustdoc-path passed"); let mut rustdoc = Command::new(rustdoc_path); - rustdoc.arg("-L").arg(aux_dir) - .arg("-o").arg(out_dir) + 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"); + rustdoc.arg(format!("-Clinker={}", linker)); } self.compose_and_run_compiler(rustdoc, None) @@ -1171,7 +1630,7 @@ actual:\n\ fn exec_compiled_test(&self) -> ProcRes { let env = &self.props.exec_env; - match &*self.config.target { + let proc_res = match &*self.config.target { // This is pretty similar to below, we're transforming: // // program arg1 arg2 @@ -1192,92 +1651,154 @@ actual:\n\ for entry in entries { let entry = entry.unwrap(); if !entry.path().is_file() { - continue + 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()); + 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) + 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_name().parent().unwrap()) + 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) + 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 `aux` directory relative to the test itself. + /// 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); + 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())) + self.fatal(&format!( + "aux-build `{}` source not found", + test_ab.display() + )) } TestPaths { file: test_ab, - base: self.testpaths.base.clone(), - relative_dir: self.testpaths.relative_dir - .join("auxiliary") - .join(rel_ab) - .parent() - .expect("aux-build path has no parent") - .to_path_buf() + 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 is_vxworks_pure_static(&self) -> bool { + if self.config.target.contains("vxworks") { + match env::var("RUST_VXWORKS_TEST_DYLINK") { + Ok(s) => s != "1", + _ => true + } + } else { + false + } + } + + fn is_vxworks_pure_dynamic(&self) -> bool { + self.config.target.contains("vxworks") && !self.is_vxworks_pure_static() + } + 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() { - create_dir_all(&self.aux_output_dir_name()).unwrap(); + let _ = fs::remove_dir_all(&aux_dir); + create_dir_all(&aux_dir).unwrap(); } - let aux_dir = self.aux_output_dir_name(); + // 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 = { - let f = self.make_lib_name(&self.testpaths.file); - let parent = f.parent().unwrap(); - TargetLocation::ThisDirectory(parent.to_path_buf()) - }; + 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 + 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 crate_type = if aux_props.no_prefer_dynamic { - None - } else if (self.config.target.contains("musl") && !aux_props.force_host) || - self.config.target.contains("wasm32") || - self.config.target.contains("emscripten") { + 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") + || self.is_vxworks_pure_static() + { // 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 @@ -1287,43 +1808,69 @@ actual:\n\ // 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. - Some("lib") + (false, Some("lib")) } else { - Some("dylib") + (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 crate::util::remove_item(&mut extern_priv, &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); + 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); + &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 = - { + 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 @@ -1347,13 +1894,22 @@ actual:\n\ let newpath = env::join_paths(&path).unwrap(); command.env(dylib_env_var(), newpath); - let mut child = command.spawn().expect(&format!("failed to exec `{:?}`", &command)); + 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(); + 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 Output { + status, + stdout, + stderr, + } = read2_abbreviated(child).expect("failed to read output"); let result = ProcRes { status, @@ -1367,15 +1923,36 @@ actual:\n\ result } - fn make_compile_args(&self, input_file: &Path, output_file: TargetLocation) -> Command { - let mut rustc = Command::new(&self.config.rustc_path); - rustc.arg(input_file) - .arg("-L").arg(&self.config.build_base); + 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 + let custom_target = self + .props + .compile_flags .iter() - .fold(false, |acc, x| acc || x.starts_with("--target")); + .any(|x| x.starts_with("--target")); if !custom_target { let target = if self.props.force_host { @@ -1386,33 +1963,51 @@ actual:\n\ rustc.arg(&format!("--target={}", target)); } + self.set_revision_flags(&mut rustc); - if let Some(revision) = self.revision { - rustc.args(&["--cfg", revision]); - } + 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 let Some(ref incremental_dir) = self.props.incremental_dir { - rustc.args(&["-Z", &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 | - ParseFail | - Incremental => { + 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"]); + "-Zdump-mir-exclude-pass-number", + ]); let mir_dump_dir = self.get_mir_dump_dir(); let _ = fs::remove_dir_all(&mir_dump_dir); @@ -1423,34 +2018,23 @@ actual:\n\ rustc.arg(dir_opt); } - RunPass | Ui => { - if !self - .props - .compile_flags - .iter() - .any(|s| s.starts_with("--error-format")) - { - rustc.args(&["--error-format", "json"]); - } - } - RunFail | - RunPassValgrind | - Pretty | - DebugInfoGdb | - DebugInfoLldb | - Codegen | - Rustdoc | - RunMake | - CodegenUnits => { + 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 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"]); + if !is_rustdoc { + if self.config.target == "wasm32-unknown-unknown" + || self.is_vxworks_pure_static() { + // 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 { @@ -1458,17 +2042,42 @@ actual:\n\ rustc.arg("-o").arg(path); } TargetLocation::ThisDirectory(path) => { - rustc.arg("--out-dir").arg(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 { - rustc.args(self.split_maybe_args(&self.config.host_rustcflags)); + self.maybe_add_external_args(&mut rustc, + self.split_maybe_args(&self.config.host_rustcflags)); } else { - rustc.args(self.split_maybe_args(&self.config.target_rustcflags)); + 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)); + } + } } - 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") + || self.is_vxworks_pure_dynamic() { + rustc.arg("-Ctarget-feature=-crt-static"); } rustc.args(&self.props.compile_flags); @@ -1476,28 +2085,19 @@ actual:\n\ rustc } - fn make_lib_name(&self, auxfile: &Path) -> PathBuf { - // what we return here is not particularly important, as it - // happens; rustc ignores everything except for the directory. - let auxname = self.output_testname(auxfile); - self.aux_output_dir_name().join(&auxname) - } - fn make_exe_name(&self) -> PathBuf { - let mut f = self.output_base_name(); + // 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") { - let mut fname = f.file_name().unwrap().to_os_string(); - fname.push(".js"); - f.set_file_name(&fname); + f = f.with_extra_extension("js"); } else if self.config.target.contains("wasm32") { - let mut fname = f.file_name().unwrap().to_os_string(); - fname.push(".wasm"); - f.set_file_name(&fname); + f = f.with_extra_extension("wasm"); } else if !env::consts::EXE_SUFFIX.is_empty() { - let mut fname = f.file_name().unwrap().to_os_string(); - fname.push(env::consts::EXE_SUFFIX); - f.set_file_name(&fname); + f = f.with_extra_extension(env::consts::EXE_SUFFIX); } f } @@ -1514,11 +2114,9 @@ actual:\n\ } else { self.fatal("no NodeJS binary found (--nodejs)"); } - } - - // If this is otherwise wasm , then run tests under nodejs with our + // If this is otherwise wasm, then run tests under nodejs with our // shim - if self.config.target.contains("wasm32") { + } else if self.config.target.contains("wasm32") { if let Some(ref p) = self.config.nodejs { args.push(p.clone()); } else { @@ -1526,7 +2124,7 @@ actual:\n\ } let src = self.config.src_base - .parent().unwrap() // chop off `run-pass` + .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()); @@ -1541,31 +2139,27 @@ actual:\n\ args.extend(self.split_maybe_args(&self.props.run_flags)); let prog = args.remove(0); - ProcArgs { - prog, - args, - } + 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 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 util; + use crate::util; // Linux and mac don't require adjusting the library search path if cfg!(unix) { @@ -1574,7 +2168,11 @@ actual:\n\ // 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!( + "{}=\"{}\"", + util::lib_path_env_var(), + util::make_new_path(path) + ) } format!("{} {:?}", lib_path_cmd_prefix(libpath), command) @@ -1593,37 +2191,52 @@ actual:\n\ self.maybe_dump_to_stdout(out, err); } - fn dump_output_file(&self, - out: &str, - extension: &str) { + fn dump_output_file(&self, out: &str, extension: &str) { let outfile = self.make_out_name(extension); - File::create(&outfile).unwrap().write_all(out.as_bytes()).unwrap(); + 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 { - let f = self.output_base_name(); - let mut fname = f.file_name().unwrap().to_os_string(); - fname.push(&format!("{}.aux", self.config.mode.disambiguator())); - f.with_file_name(&fname) + self.output_base_dir() + .join("auxiliary") + .with_extra_extension(self.config.mode.disambiguator()) } - fn output_testname(&self, filepath: &Path) -> PathBuf { - PathBuf::from(filepath.file_stem().unwrap()) + /// 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()) } - /// Given a test path like `compile-fail/foo/bar.rs` Returns a name like - /// `/foo/bar-stage1` - fn output_base_name(&self) -> PathBuf { - let dir = self.config.build_base.join(&self.testpaths.relative_dir); + /// 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()) + } - // Note: The directory `dir` is created during `collect_tests_from_dir` - dir - .join(&self.output_testname(&self.testpaths.file)) - .with_extension(&self.config.stage_id) + /// 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) { @@ -1639,94 +2252,105 @@ actual:\n\ fn error(&self, err: &str) { match self.revision { Some(rev) => println!("\nerror in revision `{}`: {}", rev, err), - None => println!("\nerror: {}", err) + None => println!("\nerror: {}", err), } } fn fatal(&self, err: &str) -> ! { - self.error(err); panic!(); + self.error(err); + error!("fatal error, panic: {:?}", err); + panic!("fatal error"); } fn fatal_proc_rec(&self, err: &str, proc_res: &ProcRes) -> ! { - self.try_print_open_handles(); self.error(err); proc_res.fatal(None); } - // This function is a poor man's attempt to debug rust-lang/rust#38620, if - // that's closed then this should be deleted - // - // This is a very "opportunistic" debugging attempt, so we ignore all - // errors here. - fn try_print_open_handles(&self) { - if !cfg!(windows) { - return - } - if self.config.mode != Incremental { - return - } - - let filename = match self.testpaths.file.file_stem() { - Some(path) => path, - None => return, - }; - - let mut cmd = Command::new("handle.exe"); - cmd.arg("-a").arg("-u"); - cmd.arg(filename); - cmd.arg("-nobanner"); - cmd.stdout(Stdio::piped()); - cmd.stderr(Stdio::piped()); - let output = match cmd.spawn().and_then(read2_abbreviated) { - Ok(output) => output, - Err(_) => return, - }; - println!("---------------------------------------------------"); - println!("ran extra command to debug rust-lang/rust#38620: "); - println!("{:?}", cmd); - println!("result: {}", output.status); - println!("--- stdout ----------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("--- stderr ----------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - println!("---------------------------------------------------"); - } - // 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_name().parent().unwrap().to_path_buf()); + 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"); + rustc.arg("-L").arg(aux_dir).arg("--emit=llvm-ir"); self.compose_and_run_compiler(rustc, None) } - fn check_ir_with_filecheck(&self) -> ProcRes { - let irfile = self.output_base_name().with_extension("ll"); + 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(irfile) + 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) { - assert!(self.revision.is_none(), "revisions not relevant here"); + 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 mut proc_res = self.compile_test_and_save_ir(); + let (proc_res, output_path) = self.compile_test_and_save_assembly(); if !proc_res.status.success() { self.fatal_proc_rec("compilation failed!", &proc_res); } - proc_res = self.check_ir_with_filecheck(); + let proc_res = self.verify_with_filecheck(&output_path); if !proc_res.status.success() { self.fatal_proc_rec("verification with 'FileCheck' failed", &proc_res); } @@ -1734,9 +2358,7 @@ actual:\n\ fn charset() -> &'static str { // FreeBSD 10.1 defaults to GDB 6.1.1 which doesn't support "auto" charset - if cfg!(target_os = "bitrig") { - "auto" - } else if cfg!(target_os = "freebsd") { + if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" @@ -1746,7 +2368,7 @@ actual:\n\ fn run_rustdoc_test(&self) { assert!(self.revision.is_none(), "revisions not relevant here"); - let out_dir = self.output_base_name(); + let out_dir = self.output_base_dir(); let _ = fs::remove_dir_all(&out_dir); create_dir_all(&out_dir).unwrap(); @@ -1771,105 +2393,117 @@ actual:\n\ } } - fn get_lines>(&self, path: &P, - mut other_files: Option<&mut Vec>) -> Vec { - let mut file = fs::File::open(path) - .expect("markdown_test_output_check_entry File::open failed"); - let mut content = String::new(); - file.read_to_string(&mut content) - .expect("markdown_test_output_check_entry read_to_string failed"); + 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() + 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))); + 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)); + 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); - } - } - } - }) {} + 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); + self.fatal_proc_rec( + &format!( + "Not found test at line{} \"{}\":{:?}", + if v.len() > 1 { "s" } else { "" }, + entry, + v + ), + &res, + ); } } } @@ -1886,32 +2520,32 @@ actual:\n\ self.check_no_compiler_crash(&proc_res); - const PREFIX: &'static str = "TRANS_ITEM "; + const PREFIX: &'static str = "MONO_ITEM "; const CGU_MARKER: &'static str = "@@"; - let actual: Vec = proc_res + let actual: Vec = proc_res .stdout .lines() .filter(|line| line.starts_with(PREFIX)) - .map(str_to_trans_item) + .map(|line| str_to_mono_item(line, true)) .collect(); - let expected: Vec = errors::load_errors(&self.testpaths.file, None) + let expected: Vec = errors::load_errors(&self.testpaths.file, None) .iter() - .map(|e| str_to_trans_item(&e.msg[..])) + .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); + 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 { + expected_item.codegen_units != actual_item.codegen_units + { wrong_cgus.push((expected_item.clone(), actual_item.clone())); } } else { @@ -1919,11 +2553,11 @@ actual:\n\ } } - let unexpected: Vec<_> = - actual.iter() - .filter(|acgu| !expected.iter().any(|ecgu| acgu.name == ecgu.name)) - .map(|acgu| acgu.string.clone()) - .collect(); + 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(); @@ -1959,63 +2593,74 @@ actual:\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!(""); + 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()) - { + if !(missing.is_empty() && unexpected.is_empty() && wrong_cgus.is_empty()) { panic!(); } #[derive(Clone, Eq, PartialEq)] - struct TransItem { + struct MonoItem { name: String, codegen_units: HashSet, string: String, } - // [TRANS_ITEM] name [@@ (cgu)+] - fn str_to_trans_item(s: &str) -> TransItem { + // [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.trim().to_owned()); + let full_string = format!("{}{}", PREFIX, s); - let parts: Vec<&str> = s.split(CGU_MARKER) - .map(str::trim) - .filter(|s| !s.is_empty()) - .collect(); + 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(str::to_owned) - .collect() - } - else { + 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() }; - TransItem { + MonoItem { name: name.to_owned(), codegen_units: cgus, string: full_string, } } - fn codegen_units_to_str(cgus: &HashSet) -> String - { + fn codegen_units_to_str(cgus: &HashSet) -> String { let mut cgus: Vec<_> = cgus.iter().collect(); cgus.sort(); @@ -2027,6 +2672,32 @@ actual:\n\ 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) { @@ -2046,14 +2717,17 @@ actual:\n\ fs::create_dir_all(&incremental_dir).unwrap(); if self.config.verbose { - print!("init_incremental_test: incremental_dir={}", incremental_dir.display()); + 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 `cfail` + // - 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 @@ -2070,11 +2744,16 @@ actual:\n\ // 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"); + 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"); + 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(); @@ -2088,7 +2767,10 @@ actual:\n\ }; if self.config.verbose { - print!("revision={:?} revision_props={:#?}", revision, revision_props); + print!( + "revision={:?} revision_props={:#?}", + revision, revision_props + ); } if revision.starts_with("rpass") { @@ -2098,8 +2780,7 @@ actual:\n\ } else if revision.starts_with("cfail") { revision_cx.run_cfail_test(); } else { - revision_cx.fatal( - "revision name must begin with rpass, rfail, or cfail"); + revision_cx.fatal("revision name must begin with rpass, rfail, or cfail"); } } @@ -2109,15 +2790,16 @@ actual:\n\ } fn run_rmake_test(&self) { - // FIXME(#11094): we should fix these tests - if self.config.host != self.config.target { - return - } - let cwd = env::current_dir().unwrap(); - let src_root = self.config.src_base.parent().unwrap() - .parent().unwrap() - .parent().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()); @@ -2127,9 +2809,11 @@ actual:\n\ create_dir_all(&tmpdir).unwrap(); let host = &self.config.host; - let make = if host.contains("bitrig") || host.contains("dragonfly") || - host.contains("freebsd") || host.contains("netbsd") || - host.contains("openbsd") { + let make = if host.contains("dragonfly") + || host.contains("freebsd") + || host.contains("netbsd") + || host.contains("openbsd") + { "gmake" } else { "make" @@ -2137,31 +2821,62 @@ actual:\n\ 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("RUSTDOC", - cwd.join(&self.config.rustdoc_path.as_ref().expect("--rustdoc-path passed"))) - .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); + .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"); - if self.config.target.contains("msvc") { + // 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"); @@ -2169,25 +2884,33 @@ actual:\n\ // 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(" "); + 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", &self.config.cxx); + .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); + .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`"); + let output = cmd + .spawn() + .and_then(read2_abbreviated) + .expect("failed to spawn `make`"); if !output.status.success() { let res = ProcRes { status: output.status, @@ -2222,6 +2945,82 @@ actual:\n\ 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 load_compare_outputs(&self, proc_res: &ProcRes, + output_kind: TestOutput, explicit_format: bool) -> usize { + + let (stderr_kind, stdout_kind) = match output_kind { + TestOutput::Compile => (UI_STDERR, UI_STDOUT), + TestOutput::Run => (UI_RUN_STDERR, UI_RUN_STDOUT) + }; + + let expected_stderr = self.load_expected_output(stderr_kind); + let expected_stdout = self.load_expected_output(stdout_kind); + + let normalized_stdout = match output_kind { + TestOutput::Run if self.config.remote_test_client.is_some() => { + // When tests are run using the remote-test-client, the string + // 'uploaded "$TEST_BUILD_DIR/, waiting for result"' + // is printed to stdout by the client and then captured in the ProcRes, + // so it needs to be removed when comparing the run-pass test execution output + lazy_static! { + static ref REMOTE_TEST_RE: Regex = Regex::new( + "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-]+)+\", waiting for result\n" + ).unwrap(); + } + REMOTE_TEST_RE.replace( + &self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout), + "" + ).to_string() + } + _ => self.normalize_output(&proc_res.stdout, &self.props.normalize_stdout) + }; + + let stderr = if explicit_format { + 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; + match output_kind { + TestOutput::Compile => { + 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); + } + } + TestOutput::Run => { + errors += self.compare_output(stdout_kind, &normalized_stdout, &expected_stdout); + errors += self.compare_output(stderr_kind, &normalized_stderr, &expected_stderr); + } + } + errors + } + fn run_ui_test(&self) { // if the user specified a format in the ui test // print the output to the stderr file, otherwise extract @@ -2232,34 +3031,18 @@ actual:\n\ .iter() .any(|s| s.contains("--error-format")); let proc_res = self.compile_test(); + self.check_if_test_should_compile(&proc_res); - let expected_stderr_path = self.expected_output_path(UI_STDERR); - let expected_stderr = self.load_expected_output(&expected_stderr_path); - - let expected_stdout_path = self.expected_output_path(UI_STDOUT); - let expected_stdout = self.load_expected_output(&expected_stdout_path); - - let expected_fixed_path = self.expected_output_path(UI_FIXED); - let expected_fixed = self.load_expected_output(&expected_fixed_path); - - 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, &proc_res) - }; - - let normalized_stderr = - self.normalize_output(&stderr, &self.props.normalize_stderr); + let expected_fixed = self.load_expected_output(UI_FIXED); - let mut errors = 0; - errors += self.compare_output(UI_STDOUT, &normalized_stdout, &expected_stdout); - errors += self.compare_output(UI_STDERR, &normalized_stderr, &expected_stderr); + let modes_to_prune = vec![CompareMode::Nll]; + self.prune_duplicate_outputs(&modes_to_prune); + let mut errors = self.load_compare_outputs(&proc_res, TestOutput::Compile, explicit); - if self.config.rustfix_coverage { + 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. // @@ -2287,13 +3070,11 @@ actual:\n\ panic!("couldn't write to {}", coverage_file_path.display()); } } - } - - if self.props.run_rustfix { - // Apply suggestions from lints to the code itself + } 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) - .expect("Could not load output from path"); + .unwrap(); let suggestions = get_suggestions_from_json( &proc_res.stderr, &HashSet::new(), @@ -2302,13 +3083,13 @@ actual:\n\ } else { Filter::Everything }, - ).expect("Could not retrieve suggestions from JSON"); + ).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(UI_FIXED, &fixed_code, &expected_fixed); + 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` \ @@ -2317,27 +3098,76 @@ actual:\n\ } if errors > 0 { - println!("To update references, run this command from build directory:"); - let relative_path_to_file = - self.testpaths.relative_dir - .join(self.testpaths.file.file_name().unwrap()); - println!("{}/update-references.sh '{}' '{}'", - self.config.src_base.display(), - self.config.build_base.display(), - relative_path_to_file.display()); - self.fatal_proc_rec(&format!("{} errors occurred comparing output.", errors), - &proc_res); - } - - if self.props.run_pass { + 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() { let proc_res = self.exec_compiled_test(); + let run_output_errors = if self.props.check_run_results { + self.load_compare_outputs(&proc_res, TestOutput::Run, explicit) + } else { + 0 + }; + if run_output_errors > 0 { + self.fatal_proc_rec( + &format!("{} errors occured comparing run output.", run_output_errors), + &proc_res, + ); + } + if self.should_run_successfully() { + if !proc_res.status.success() { + self.fatal_proc_rec("test run failed!", &proc_res); + } + } else { + if proc_res.status.success() { + self.fatal_proc_rec("test run succeeded!", &proc_res); + } + } + if !self.props.error_patterns.is_empty() { + // "// error-pattern" comments + self.check_error_patterns(&proc_res.stderr, &proc_res); + } + } - 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() { + let check_patterns = + !self.should_run() && + !self.props.error_patterns.is_empty(); + + let check_annotations = + !check_patterns || + !expected_errors.is_empty(); + + if check_patterns { + // "// error-pattern" comments + self.check_error_patterns(&proc_res.stderr, &proc_res); + } + + if check_annotations { + // "//~ERROR comments" + self.check_expected_errors(expected_errors, &proc_res); } } - if self.props.run_rustfix { + 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( @@ -2371,14 +3201,11 @@ actual:\n\ } fn check_mir_dump(&self) { - let mut test_file_contents = String::new(); - fs::File::open(self.testpaths.file.clone()).unwrap() - .read_to_string(&mut test_file_contents) - .unwrap(); - if let Some(idx) = test_file_contents.find("// END RUST SOURCE") { + 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: Option<&str> = None; let mut curr_test_contents = vec![ExpectedLine::Elision]; for l in tests_text_str.lines() { debug!("line: {:?}", l); @@ -2407,14 +3234,20 @@ actual:\n\ } fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Path) { - let t = |file| FileTime::from_last_modification_time(&fs::metadata(file).unwrap()); + 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); + 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 + ); } } @@ -2425,22 +3258,28 @@ actual:\n\ 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()); + 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 mut dumped_file = fs::File::open(output_file.clone()).unwrap(); - let mut dumped_string = String::new(); - dumped_file.read_to_string(&mut dumped_string).unwrap(); - let mut dumped_lines = dumped_string.lines().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 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); @@ -2451,27 +3290,29 @@ actual:\n\ }; 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 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 + &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\ - Actual Line: {:?}\n\ - Expected:\n{}\n\ - Actual:\n{}", - extra_msg, - expected_line, - expected_content, - normalize_all); + 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 @@ -2487,11 +3328,18 @@ actual:\n\ if !compare(expected_line, dumped_line) { error!("{:?}", start_block_line); - error(expected_line, - format!("Mismatch in lines\nCurrnt block: {}\nExpected Line: {:?}", - start_block_line.unwrap_or("None"), dumped_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() { @@ -2512,8 +3360,8 @@ actual:\n\ error(expected_line, "ran out of mir dump to match against".into()); } } - }, - None => {}, + } + None => {} } } } @@ -2527,17 +3375,40 @@ actual:\n\ } fn normalize_output(&self, output: &str, custom_rules: &[(String, String)]) -> String { - let parent_dir = self.testpaths.file.parent().unwrap(); let cflags = self.props.compile_flags.join(" "); - let json = cflags.contains("--error-format json") || - cflags.contains("--error-format pretty-json"); - let parent_dir_str = if json { - parent_dir.display().to_string().replace("\\", "\\\\") - } else { - parent_dir.display().to_string() + 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 mut normalized = output.replace(&parent_dir_str, "$DIR"); + 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 @@ -2547,10 +3418,21 @@ actual:\n\ normalized = normalized.replace("\\n", "\n"); } - normalized = normalized.replace("\\\\", "\\") // denormalize for paths on windows - .replace("\\", "/") // normalize for paths on windows - .replace("\r\n", "\n") // normalize for linebreaks on windows - .replace("\t", "\\t"); // makes tabs visible + // 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(); @@ -2558,26 +3440,71 @@ actual:\n\ normalized } - fn expected_output_path(&self, kind: &str) -> PathBuf { - let extension = match self.revision { - Some(r) => format!("{}.{}", r, kind), - None => kind.to_string(), - }; - self.testpaths.file.with_extension(extension) + /// 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 load_expected_output(&self, path: &Path) -> String { + 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() { - return String::new(); + path = expected_output_path(&self.testpaths, self.revision, &None, kind); } - let mut result = String::new(); - match File::open(path).and_then(|mut f| f.read_to_string(&mut result)) { - Ok(_) => result, - Err(e) => { - self.fatal(&format!("failed to load expected output from `{}`: {}", - path.display(), e)) + 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() } } @@ -2587,35 +3514,129 @@ actual:\n\ }) } + 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; } - println!("normalized {}:\n{}\n", kind, actual); - println!("expected {}:\n{}\n", kind, expected); - println!("diff of {}:\n", kind); - - for diff in diff::lines(expected, actual) { - match diff { - diff::Result::Left(l) => println!("-{}", l), - diff::Result::Both(l, _) => println!(" {}", l), - diff::Result::Right(r) => println!("+{}", r), + 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 output_file = self.output_base_name().with_extension(kind); - match File::create(&output_file).and_then(|mut f| f.write_all(actual.as_bytes())) { - Ok(()) => { } - Err(e) => { - self.fatal(&format!("failed to write {} to `{}`: {}", - kind, output_file.display(), e)) + 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); - println!("Actual {} saved to {}", kind, output_file.display()); - 1 + 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(); } } @@ -2636,20 +3657,23 @@ impl ProcRes { 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, self.stdout, - self.stderr); + 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(())); @@ -2664,14 +3688,14 @@ enum TargetLocation { #[derive(Clone, PartialEq, Eq)] enum ExpectedLine> { Elision, - Text(T) + Text(T), } impl fmt::Debug for ExpectedLine where - T: AsRef + fmt::Debug + T: AsRef + fmt::Debug, { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { if let &ExpectedLine::Text(ref t) = self { write!(formatter, "{:?}", t) } else { @@ -2694,8 +3718,8 @@ fn nocomment_mir_line(line: &str) -> &str { } fn read2_abbreviated(mut child: Child) -> io::Result { + use crate::read2::read2; use std::mem::replace; - use read2::read2; const HEAD_LEN: usize = 160 * 1024; const TAIL_LEN: usize = 256 * 1024; @@ -2706,7 +3730,7 @@ fn read2_abbreviated(mut child: Child) -> io::Result { head: Vec, skipped: usize, tail: Box<[u8]>, - } + }, } impl ProcOutput { @@ -2721,9 +3745,17 @@ fn read2_abbreviated(mut child: Child) -> io::Result { 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 { + head, + skipped, + tail, + } } - ProcOutput::Abbreviated { ref mut skipped, ref mut tail, .. } => { + ProcOutput::Abbreviated { + ref mut skipped, + ref mut tail, + .. + } => { *skipped += data.len(); if data.len() <= TAIL_LEN { tail[..data.len()].copy_from_slice(data); @@ -2740,7 +3772,11 @@ fn read2_abbreviated(mut child: Child) -> io::Result { fn into_bytes(self) -> Vec { match self { ProcOutput::Full(bytes) => bytes, - ProcOutput::Abbreviated { mut head, skipped, tail } => { + ProcOutput::Abbreviated { + mut head, + skipped, + tail, + } => { write!(&mut head, "\n\n<<<<<< SKIPPED {} BYTES >>>>>>\n\n", skipped).unwrap(); head.extend_from_slice(&tail); head @@ -2753,10 +3789,14 @@ fn read2_abbreviated(mut child: Child) -> io::Result { 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(); - })?; + 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 { diff --git a/src/runtest/tests.rs b/src/runtest/tests.rs new file mode 100644 index 0000000..79128aa --- /dev/null +++ b/src/runtest/tests.rs @@ -0,0 +1,61 @@ +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/tests.rs b/src/tests.rs new file mode 100644 index 0000000..388ad75 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,51 @@ +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/util.rs b/src/util.rs index c00f28e..4f5a22b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,33 +1,39 @@ -// Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - +use std::ffi::OsStr; use std::env; -use common::Config; +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"), - ("bitrig", "bitrig"), + ("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"), - ("solaris", "solaris"), - ("emscripten", "emscripten"), + ("vxworks", "vxworks"), ]; const ARCH_TABLE: &'static [(&'static str, &'static str)] = &[ @@ -35,46 +41,76 @@ const ARCH_TABLE: &'static [(&'static str, &'static str)] = &[ ("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"), - ("asmjs", "asmjs"), - ("wasm32", "wasm32"), ]; 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" + return name == "emscripten" || name == "wasm32-bare"; } + let triple: Vec<_> = triple.split('-').collect(); for &(triple_os, os) in OS_TABLE { - if triple.contains(triple_os) { + 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) { + 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 matches_env(triple: &str, name: &str) -> bool { + if let Some(env) = triple.split('-').nth(3) { + env.starts_with(name) + } else { + false + } } pub fn get_pointer_width(triple: &str) -> &'static str { @@ -108,3 +144,31 @@ pub fn logv(config: &Config, s: String) { 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) + } + } +} + +pub fn remove_item(v: &mut Vec, item: &T) -> Option { + let pos = match v.iter().position(|x| *x == *item) { + Some(x) => x, + None => return None, + }; + Some(v.remove(pos)) +} diff --git a/src/util/tests.rs b/src/util/tests.rs new file mode 100644 index 0000000..55bf659 --- /dev/null +++ b/src/util/tests.rs @@ -0,0 +1,32 @@ +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")); +}