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] = &[
49 let dep_dir = cargo::TARGET_LIB.join("deps");
50 let mut crates: HashMap<&str, Vec<PathBuf>> = HashMap::with_capacity(CRATES.len());
51 let mut flags = String::new();
52 for entry in fs::read_dir(dep_dir).unwrap().flatten() {
53 let path = entry.path();
54 if let Some(name) = try {
55 let name = path.file_name()?.to_str()?;
56 let (name, _) = name.strip_suffix(".rlib")?.strip_prefix("lib")?.split_once('-')?;
57 CRATES.iter().copied().find(|&c| c == name)?
59 flags += &format!(" --extern {}={}", name, path.display());
60 crates.entry(name).or_default().push(path.clone());
63 crates.retain(|_, paths| paths.len() > 1);
64 if !crates.is_empty() {
65 let crate_names = crates.keys().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", ");
66 // add backslashes for an easy copy-paste `rm` command
70 .map(|p| strip_current_dir(&p).display().to_string())
73 // Check which action should be done in order to remove compiled deps.
74 // If pre-installed version of compiler is used, `cargo clean` will do.
75 // Otherwise (for bootstrapped compiler), the dependencies directory
76 // must be removed manually.
77 let suggested_action = if std::env::var_os("RUSTC_BOOTSTRAP").is_some() {
78 "removing the stageN-tools directory"
80 "running `cargo clean`"
84 "\n----------------------------------------------------------------------\n\
85 ERROR: Found multiple rlibs for crates: {}\n\
86 Try {} or remove the following files:\n\n{}\n\n\
87 For details on this error see https://github.com/rust-lang/rust-clippy/issues/7343\n\
88 ----------------------------------------------------------------------\n",
89 crate_names, suggested_action, paths
95 fn default_config() -> compiletest::Config {
96 let mut config = compiletest::Config::default();
98 if let Ok(filters) = env::var("TESTNAME") {
99 config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
102 if let Some(path) = option_env!("RUSTC_LIB_PATH") {
103 let path = PathBuf::from(path);
104 config.run_lib_path = path.clone();
105 config.compile_lib_path = path;
108 config.target_rustcflags = Some(format!(
109 "--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}",
110 host_lib().join("deps").display(),
111 cargo::TARGET_LIB.join("deps").display(),
112 third_party_crates(),
115 config.build_base = host_lib().join("test_build_base");
116 config.rustc_path = clippy_driver_path();
120 fn run_ui(cfg: &mut compiletest::Config) {
121 cfg.mode = TestMode::Ui;
122 cfg.src_base = Path::new("tests").join("ui");
123 // use tests/clippy.toml
124 let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
125 compiletest::run_tests(cfg);
128 fn run_internal_tests(cfg: &mut compiletest::Config) {
129 // only run internal tests with the internal-tests feature
130 if !RUN_INTERNAL_TESTS {
133 cfg.mode = TestMode::Ui;
134 cfg.src_base = Path::new("tests").join("ui-internal");
135 compiletest::run_tests(cfg);
138 fn run_ui_toml(config: &mut compiletest::Config) {
139 fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
140 let mut result = true;
141 let opts = compiletest::test_opts(config);
142 for dir in fs::read_dir(&config.src_base)? {
144 if !dir.file_type()?.is_dir() {
147 let dir_path = dir.path();
148 let _g = VarGuard::set("CARGO_MANIFEST_DIR", &dir_path);
149 for file in fs::read_dir(&dir_path)? {
151 let file_path = file.path();
152 if file.file_type()?.is_dir() {
155 if file_path.extension() != Some(OsStr::new("rs")) {
158 let paths = compiletest::common::TestPaths {
160 base: config.src_base.clone(),
161 relative_dir: dir_path.file_name().unwrap().into(),
163 let test_name = compiletest::make_test_name(config, &paths);
166 .position(|test| test.desc.name == test_name)
167 .expect("The test should be in there");
168 result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
174 config.mode = TestMode::Ui;
175 config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap();
177 let tests = compiletest::make_tests(config);
179 let res = run_tests(config, tests);
182 Ok(false) => panic!("Some tests failed"),
184 panic!("I/O failure during tests: {:?}", e);
189 fn run_ui_cargo(config: &mut compiletest::Config) {
191 config: &compiletest::Config,
193 mut tests: Vec<tester::TestDescAndFn>,
194 ) -> Result<bool, io::Error> {
195 let mut result = true;
196 let opts = compiletest::test_opts(config);
198 for dir in fs::read_dir(&config.src_base)? {
200 if !dir.file_type()?.is_dir() {
204 // Use the filter if provided
205 let dir_path = dir.path();
206 for filter in filters {
207 if !dir_path.ends_with(filter) {
212 for case in fs::read_dir(&dir_path)? {
214 if !case.file_type()?.is_dir() {
218 let src_path = case.path().join("src");
220 // When switching between branches, if the previous branch had a test
221 // that the current branch does not have, the directory is not removed
222 // because an ignored Cargo.lock file exists.
223 if !src_path.exists() {
227 env::set_current_dir(&src_path)?;
228 for file in fs::read_dir(&src_path)? {
230 if file.file_type()?.is_dir() {
234 // Search for the main file to avoid running a test for each file in the project
235 let file_path = file.path();
236 match file_path.file_name().and_then(OsStr::to_str) {
237 Some("main.rs") => {},
240 let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
241 let paths = compiletest::common::TestPaths {
243 base: config.src_base.clone(),
244 relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
246 let test_name = compiletest::make_test_name(config, &paths);
249 .position(|test| test.desc.name == test_name)
250 .expect("The test should be in there");
251 result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
258 if cargo::is_rustc_test_suite() {
262 config.mode = TestMode::Ui;
263 config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
265 let tests = compiletest::make_tests(config);
267 let current_dir = env::current_dir().unwrap();
268 let res = run_tests(config, &config.filters, tests);
269 env::set_current_dir(current_dir).unwrap();
273 Ok(false) => panic!("Some tests failed"),
275 panic!("I/O failure during tests: {:?}", e);
281 set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
282 set_var("__CLIPPY_INTERNAL_TESTS", "true");
283 //set_var("RUST_BACKTRACE", "0");
289 let mut config = default_config();
291 run_ui_toml(&mut config);
292 run_ui_cargo(&mut config);
293 run_internal_tests(&mut config);
296 /// Restores an env var on drop
300 value: Option<OsString>,
304 fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
305 let value = var_os(key);
311 impl Drop for VarGuard {
313 match self.value.as_deref() {
314 None => remove_var(self.key),
315 Some(value) => set_var(self.key, value),
320 fn strip_current_dir(path: &Path) -> &Path {
321 if let Ok(curr) = env::current_dir() {
322 if let Ok(stripped) = path.strip_prefix(curr) {