X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=tests%2Fcompile-test.rs;h=e8b1640c8693e5907cf93e491a713658b17e6a63;hb=58969807ab7a79caecf33ad8a03df8a9793a23ad;hp=e1110721f6ece396b3eef67475f6598b84c13a58;hpb=72be4764c57266dc1e71d32a06619d302b3253a4;p=rust.git diff --git a/tests/compile-test.rs b/tests/compile-test.rs index e1110721f6e..e8b1640c869 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -1,11 +1,13 @@ #![feature(test)] // compiletest_rs requires this attribute -#![feature(once_cell)] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +#![warn(rust_2018_idioms, unused_lifetimes)] use compiletest_rs as compiletest; use compiletest_rs::common::Mode as TestMode; -use std::env::{self, set_var, var}; -use std::ffi::OsStr; +use std::collections::HashMap; +use std::env::{self, remove_var, set_var, var_os}; +use std::ffi::{OsStr, OsString}; use std::fs; use std::io; use std::path::{Path, PathBuf}; @@ -15,52 +17,90 @@ // whether to run internal tests or not const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints"); -fn host_lib() -> PathBuf { - option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from) -} - -fn clippy_driver_path() -> PathBuf { - option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from) -} - -// When we'll want to use `extern crate ..` for a dependency that is used -// both by the crate and the compiler itself, we can't simply pass -L flags -// as we'll get a duplicate matching versions. Instead, disambiguate with -// `--extern dep=path`. -// See https://github.com/rust-lang/rust-clippy/issues/4015. -// -// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files. -// Because it would force-rebuild if the options passed to `build` command is not the same -// as what we manually pass to `cargo` invocation -fn third_party_crates() -> String { - use std::collections::HashMap; - static CRATES: &[&str] = &["serde", "serde_derive", "regex", "clippy_lints", "syn", "quote"]; - let dep_dir = cargo::TARGET_LIB.join("deps"); - let mut crates: HashMap<&str, PathBuf> = HashMap::with_capacity(CRATES.len()); - for entry in fs::read_dir(dep_dir).unwrap() { - let path = match entry { - Ok(entry) => entry.path(), - Err(_) => continue, +/// All crates used in UI tests are listed here +static TEST_DEPENDENCIES: &[&str] = &[ + "clippy_utils", + "derive_new", + "if_chain", + "itertools", + "quote", + "regex", + "serde", + "serde_derive", + "syn", +]; + +// Test dependencies may need an `extern crate` here to ensure that they show up +// in the depinfo file (otherwise cargo thinks they are unused) +#[allow(unused_extern_crates)] +extern crate clippy_utils; +#[allow(unused_extern_crates)] +extern crate derive_new; +#[allow(unused_extern_crates)] +extern crate if_chain; +#[allow(unused_extern_crates)] +extern crate itertools; +#[allow(unused_extern_crates)] +extern crate quote; +#[allow(unused_extern_crates)] +extern crate syn; + +/// Produces a string with an `--extern` flag for all UI test crate +/// dependencies. +/// +/// The dependency files are located by parsing the depinfo file for this test +/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test +/// dependencies must be added to Cargo.toml at the project root. Test +/// dependencies that are not *directly* used by this test module require an +/// `extern crate` declaration. +fn extern_flags() -> String { + let current_exe_depinfo = { + let mut path = env::current_exe().unwrap(); + path.set_extension("d"); + std::fs::read_to_string(path).unwrap() + }; + let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len()); + for line in current_exe_depinfo.lines() { + // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:` + let parse_name_path = || { + if line.starts_with(char::is_whitespace) { + return None; + } + let path_str = line.strip_suffix(':')?; + let path = Path::new(path_str); + if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") { + return None; + } + let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?; + // the "lib" prefix is not present for dll files + let name = name.strip_prefix("lib").unwrap_or(name); + Some((name, path_str)) }; - if let Some(name) = path.file_name().and_then(OsStr::to_str) { - for dep in CRATES { - if name.starts_with(&format!("lib{}-", dep)) - && name.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rlib")) == Some(true) - { - if let Some(old) = crates.insert(dep, path.clone()) { - panic!("Found multiple rlibs for crate `{}`: `{:?}` and `{:?}", dep, old, path); - } - break; - } + if let Some((name, path)) = parse_name_path() { + if TEST_DEPENDENCIES.contains(&name) { + // A dependency may be listed twice if it is available in sysroot, + // and the sysroot dependencies are listed first. As of the writing, + // this only seems to apply to if_chain. + crates.insert(name, path); } } } - - let v: Vec<_> = crates - .into_iter() - .map(|(dep, path)| format!("--extern {}={}", dep, path.display())) + let not_found: Vec<&str> = TEST_DEPENDENCIES + .iter() + .copied() + .filter(|n| !crates.contains_key(n)) .collect(); - v.join(" ") + assert!( + not_found.is_empty(), + "dependencies not found in depinfo: {:?}\n\ + help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\ + help: Try adding to dev-dependencies in Cargo.toml", + not_found + ); + crates + .into_iter() + .map(|(name, path)| format!(" --extern {}={}", name, path)) + .collect() } fn default_config() -> compiletest::Config { @@ -75,22 +115,37 @@ fn default_config() -> compiletest::Config { config.run_lib_path = path.clone(); config.compile_lib_path = path; } - + let current_exe_path = std::env::current_exe().unwrap(); + let deps_path = current_exe_path.parent().unwrap(); + let profile_path = deps_path.parent().unwrap(); + + // Using `-L dependency={}` enforces that external dependencies are added with `--extern`. + // This is valuable because a) it allows us to monitor what external dependencies are used + // and b) it ensures that conflicting rlibs are resolved properly. + let host_libs = option_env!("HOST_LIBS") + .map(|p| format!(" -L dependency={}", Path::new(p).join("deps").display())) + .unwrap_or_default(); config.target_rustcflags = Some(format!( - "--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}", - host_lib().join("deps").display(), - cargo::TARGET_LIB.join("deps").display(), - third_party_crates(), + "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}", + deps_path.display(), + host_libs, + extern_flags(), )); - config.build_base = host_lib().join("test_build_base"); - config.rustc_path = clippy_driver_path(); + config.build_base = profile_path.join("test"); + config.rustc_path = profile_path.join(if cfg!(windows) { + "clippy-driver.exe" + } else { + "clippy-driver" + }); config } -fn run_mode(cfg: &mut compiletest::Config) { +fn run_ui(cfg: &mut compiletest::Config) { cfg.mode = TestMode::Ui; cfg.src_base = Path::new("tests").join("ui"); + // use tests/clippy.toml + let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap()); compiletest::run_tests(cfg); } @@ -114,7 +169,7 @@ fn run_tests(config: &compiletest::Config, mut tests: Vec continue; } let dir_path = dir.path(); - set_var("CARGO_MANIFEST_DIR", &dir_path); + let _g = VarGuard::set("CARGO_MANIFEST_DIR", &dir_path); for file in fs::read_dir(&dir_path)? { let file = file?; let file_path = file.path(); @@ -145,9 +200,7 @@ fn run_tests(config: &compiletest::Config, mut tests: Vec let tests = compiletest::make_tests(config); - let manifest_dir = var("CARGO_MANIFEST_DIR").unwrap_or_default(); let res = run_tests(config, tests); - set_var("CARGO_MANIFEST_DIR", &manifest_dir); match res { Ok(true) => {}, Ok(false) => panic!("Some tests failed"), @@ -208,7 +261,7 @@ fn run_tests( Some("main.rs") => {}, _ => continue, } - set_var("CLIPPY_CONF_DIR", case.path()); + let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path()); let paths = compiletest::common::TestPaths { file: file_path, base: config.src_base.clone(), @@ -236,10 +289,8 @@ fn run_tests( let tests = compiletest::make_tests(config); let current_dir = env::current_dir().unwrap(); - let conf_dir = var("CLIPPY_CONF_DIR").unwrap_or_default(); let res = run_tests(config, &config.filters, tests); env::set_current_dir(current_dir).unwrap(); - set_var("CLIPPY_CONF_DIR", conf_dir); match res { Ok(true) => {}, @@ -260,8 +311,32 @@ fn prepare_env() { fn compile_test() { prepare_env(); let mut config = default_config(); - run_mode(&mut config); + run_ui(&mut config); run_ui_toml(&mut config); run_ui_cargo(&mut config); run_internal_tests(&mut config); } + +/// Restores an env var on drop +#[must_use] +struct VarGuard { + key: &'static str, + value: Option, +} + +impl VarGuard { + fn set(key: &'static str, val: impl AsRef) -> Self { + let value = var_os(key); + set_var(key, val); + Self { key, value } + } +} + +impl Drop for VarGuard { + fn drop(&mut self) { + match self.value.as_deref() { + None => remove_var(self.key), + Some(value) => set_var(self.key, value), + } + } +}