1 #![feature(test)] // compiletest_rs requires this attribute
3 #![feature(try_blocks)]
5 use compiletest_rs as compiletest;
6 use compiletest_rs::common::Mode as TestMode;
8 use std::env::{self, remove_var, set_var, var_os};
9 use std::ffi::{OsStr, OsString};
12 use std::path::{Path, PathBuf};
16 // whether to run internal tests or not
17 const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
19 fn host_lib() -> PathBuf {
20 option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
23 fn clippy_driver_path() -> PathBuf {
24 option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
27 // When we'll want to use `extern crate ..` for a dependency that is used
28 // both by the crate and the compiler itself, we can't simply pass -L flags
29 // as we'll get a duplicate matching versions. Instead, disambiguate with
30 // `--extern dep=path`.
31 // See https://github.com/rust-lang/rust-clippy/issues/4015.
33 // FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files.
34 // Because it would force-rebuild if the options passed to `build` command is not the same
35 // as what we manually pass to `cargo` invocation
36 fn third_party_crates() -> String {
37 use std::collections::HashMap;
38 static CRATES: &[&str] = &[
48 let dep_dir = cargo::TARGET_LIB.join("deps");
49 let mut crates: HashMap<&str, Vec<PathBuf>> = HashMap::with_capacity(CRATES.len());
50 let mut flags = String::new();
51 for entry in fs::read_dir(dep_dir).unwrap().flatten() {
52 let path = entry.path();
53 if let Some(name) = try {
54 let name = path.file_name()?.to_str()?;
55 let (name, _) = name.strip_suffix(".rlib")?.strip_prefix("lib")?.split_once('-')?;
56 CRATES.iter().copied().find(|&c| c == name)?
58 flags += &format!(" --extern {}={}", name, path.display());
59 crates.entry(name).or_default().push(path.clone());
62 crates.retain(|_, paths| paths.len() > 1);
63 if !crates.is_empty() {
64 let crate_names = crates.keys().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", ");
65 // add backslashes for an easy copy-paste `rm` command
69 .map(|p| strip_current_dir(&p).display().to_string())
72 // Check which action should be done in order to remove compiled deps.
73 // If pre-installed version of compiler is used, `cargo clean` will do.
74 // Otherwise (for bootstrapped compiler), the dependencies directory
75 // must be removed manually.
76 let suggested_action = if std::env::var_os("RUSTC_BOOTSTRAP").is_some() {
77 "removing the stageN-tools directory"
79 "running `cargo clean`"
83 "\n----------------------------------------------------------------------\n\
84 ERROR: Found multiple rlibs for crates: {}\n\
85 Try {} or remove the following files:\n\n{}\n\n\
86 For details on this error see https://github.com/rust-lang/rust-clippy/issues/7343\n\
87 ----------------------------------------------------------------------\n",
88 crate_names, suggested_action, paths
94 fn default_config() -> compiletest::Config {
95 let mut config = compiletest::Config::default();
97 if let Ok(filters) = env::var("TESTNAME") {
98 config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
101 if let Some(path) = option_env!("RUSTC_LIB_PATH") {
102 let path = PathBuf::from(path);
103 config.run_lib_path = path.clone();
104 config.compile_lib_path = path;
107 config.target_rustcflags = Some(format!(
108 "--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}",
109 host_lib().join("deps").display(),
110 cargo::TARGET_LIB.join("deps").display(),
111 third_party_crates(),
114 config.build_base = host_lib().join("test_build_base");
115 config.rustc_path = clippy_driver_path();
119 fn run_ui(cfg: &mut compiletest::Config) {
120 cfg.mode = TestMode::Ui;
121 cfg.src_base = Path::new("tests").join("ui");
122 // use tests/clippy.toml
123 let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
124 compiletest::run_tests(cfg);
127 fn run_internal_tests(cfg: &mut compiletest::Config) {
128 // only run internal tests with the internal-tests feature
129 if !RUN_INTERNAL_TESTS {
132 cfg.mode = TestMode::Ui;
133 cfg.src_base = Path::new("tests").join("ui-internal");
134 compiletest::run_tests(cfg);
137 fn run_ui_toml(config: &mut compiletest::Config) {
138 fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
139 let mut result = true;
140 let opts = compiletest::test_opts(config);
141 for dir in fs::read_dir(&config.src_base)? {
143 if !dir.file_type()?.is_dir() {
146 let dir_path = dir.path();
147 let _g = VarGuard::set("CARGO_MANIFEST_DIR", &dir_path);
148 for file in fs::read_dir(&dir_path)? {
150 let file_path = file.path();
151 if file.file_type()?.is_dir() {
154 if file_path.extension() != Some(OsStr::new("rs")) {
157 let paths = compiletest::common::TestPaths {
159 base: config.src_base.clone(),
160 relative_dir: dir_path.file_name().unwrap().into(),
162 let test_name = compiletest::make_test_name(config, &paths);
165 .position(|test| test.desc.name == test_name)
166 .expect("The test should be in there");
167 result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
173 config.mode = TestMode::Ui;
174 config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap();
176 let tests = compiletest::make_tests(config);
178 let res = run_tests(config, tests);
181 Ok(false) => panic!("Some tests failed"),
183 panic!("I/O failure during tests: {:?}", e);
188 fn run_ui_cargo(config: &mut compiletest::Config) {
190 config: &compiletest::Config,
192 mut tests: Vec<tester::TestDescAndFn>,
193 ) -> Result<bool, io::Error> {
194 let mut result = true;
195 let opts = compiletest::test_opts(config);
197 for dir in fs::read_dir(&config.src_base)? {
199 if !dir.file_type()?.is_dir() {
203 // Use the filter if provided
204 let dir_path = dir.path();
205 for filter in filters {
206 if !dir_path.ends_with(filter) {
211 for case in fs::read_dir(&dir_path)? {
213 if !case.file_type()?.is_dir() {
217 let src_path = case.path().join("src");
219 // When switching between branches, if the previous branch had a test
220 // that the current branch does not have, the directory is not removed
221 // because an ignored Cargo.lock file exists.
222 if !src_path.exists() {
226 env::set_current_dir(&src_path)?;
227 for file in fs::read_dir(&src_path)? {
229 if file.file_type()?.is_dir() {
233 // Search for the main file to avoid running a test for each file in the project
234 let file_path = file.path();
235 match file_path.file_name().and_then(OsStr::to_str) {
236 Some("main.rs") => {},
239 let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
240 let paths = compiletest::common::TestPaths {
242 base: config.src_base.clone(),
243 relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
245 let test_name = compiletest::make_test_name(config, &paths);
248 .position(|test| test.desc.name == test_name)
249 .expect("The test should be in there");
250 result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
257 if cargo::is_rustc_test_suite() {
261 config.mode = TestMode::Ui;
262 config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
264 let tests = compiletest::make_tests(config);
266 let current_dir = env::current_dir().unwrap();
267 let res = run_tests(config, &config.filters, tests);
268 env::set_current_dir(current_dir).unwrap();
272 Ok(false) => panic!("Some tests failed"),
274 panic!("I/O failure during tests: {:?}", e);
280 set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
281 set_var("__CLIPPY_INTERNAL_TESTS", "true");
282 //set_var("RUST_BACKTRACE", "0");
288 let mut config = default_config();
290 run_ui_toml(&mut config);
291 run_ui_cargo(&mut config);
292 run_internal_tests(&mut config);
295 /// Restores an env var on drop
299 value: Option<OsString>,
303 fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
304 let value = var_os(key);
310 impl Drop for VarGuard {
312 match self.value.as_deref() {
313 None => remove_var(self.key),
314 Some(value) => set_var(self.key, value),
319 fn strip_current_dir(path: &Path) -> &Path {
320 if let Ok(curr) = env::current_dir() {
321 if let Ok(stripped) = path.strip_prefix(curr) {