]> git.lizzy.rs Git - rust.git/blob - tests/compile-test.rs
Use the precise namespace for `Reverse`
[rust.git] / tests / compile-test.rs
1 #![feature(test)] // compiletest_rs requires this attribute
2 #![feature(once_cell)]
3 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
4 #![warn(rust_2018_idioms, unused_lifetimes)]
5
6 use compiletest_rs as compiletest;
7 use compiletest_rs::common::Mode as TestMode;
8
9 use std::collections::HashMap;
10 use std::env::{self, remove_var, set_var, var_os};
11 use std::ffi::{OsStr, OsString};
12 use std::fs;
13 use std::io;
14 use std::lazy::SyncLazy;
15 use std::path::{Path, PathBuf};
16 use test_utils::IS_RUSTC_TEST_SUITE;
17
18 mod test_utils;
19
20 // whether to run internal tests or not
21 const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
22
23 /// All crates used in UI tests are listed here
24 static TEST_DEPENDENCIES: &[&str] = &[
25     "clippy_utils",
26     "derive_new",
27     "futures",
28     "if_chain",
29     "itertools",
30     "quote",
31     "regex",
32     "serde",
33     "serde_derive",
34     "syn",
35     "tokio",
36     "parking_lot",
37 ];
38
39 // Test dependencies may need an `extern crate` here to ensure that they show up
40 // in the depinfo file (otherwise cargo thinks they are unused)
41 #[allow(unused_extern_crates)]
42 extern crate clippy_utils;
43 #[allow(unused_extern_crates)]
44 extern crate derive_new;
45 #[allow(unused_extern_crates)]
46 extern crate futures;
47 #[allow(unused_extern_crates)]
48 extern crate if_chain;
49 #[allow(unused_extern_crates)]
50 extern crate itertools;
51 #[allow(unused_extern_crates)]
52 extern crate parking_lot;
53 #[allow(unused_extern_crates)]
54 extern crate quote;
55 #[allow(unused_extern_crates)]
56 extern crate syn;
57 #[allow(unused_extern_crates)]
58 extern crate tokio;
59
60 /// Produces a string with an `--extern` flag for all UI test crate
61 /// dependencies.
62 ///
63 /// The dependency files are located by parsing the depinfo file for this test
64 /// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
65 /// dependencies must be added to Cargo.toml at the project root. Test
66 /// dependencies that are not *directly* used by this test module require an
67 /// `extern crate` declaration.
68 static EXTERN_FLAGS: SyncLazy<String> = SyncLazy::new(|| {
69     let current_exe_depinfo = {
70         let mut path = env::current_exe().unwrap();
71         path.set_extension("d");
72         fs::read_to_string(path).unwrap()
73     };
74     let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
75     for line in current_exe_depinfo.lines() {
76         // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
77         let parse_name_path = || {
78             if line.starts_with(char::is_whitespace) {
79                 return None;
80             }
81             let path_str = line.strip_suffix(':')?;
82             let path = Path::new(path_str);
83             if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
84                 return None;
85             }
86             let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
87             // the "lib" prefix is not present for dll files
88             let name = name.strip_prefix("lib").unwrap_or(name);
89             Some((name, path_str))
90         };
91         if let Some((name, path)) = parse_name_path() {
92             if TEST_DEPENDENCIES.contains(&name) {
93                 // A dependency may be listed twice if it is available in sysroot,
94                 // and the sysroot dependencies are listed first. As of the writing,
95                 // this only seems to apply to if_chain.
96                 crates.insert(name, path);
97             }
98         }
99     }
100     let not_found: Vec<&str> = TEST_DEPENDENCIES
101         .iter()
102         .copied()
103         .filter(|n| !crates.contains_key(n))
104         .collect();
105     assert!(
106         not_found.is_empty(),
107         "dependencies not found in depinfo: {:?}\n\
108         help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\
109         help: Try adding to dev-dependencies in Cargo.toml",
110         not_found
111     );
112     crates
113         .into_iter()
114         .map(|(name, path)| format!(" --extern {}={}", name, path))
115         .collect()
116 });
117
118 fn base_config(test_dir: &str) -> compiletest::Config {
119     let mut config = compiletest::Config {
120         edition: Some("2021".into()),
121         mode: TestMode::Ui,
122         ..compiletest::Config::default()
123     };
124
125     if let Ok(filters) = env::var("TESTNAME") {
126         config.filters = filters.split(',').map(ToString::to_string).collect();
127     }
128
129     if let Some(path) = option_env!("RUSTC_LIB_PATH") {
130         let path = PathBuf::from(path);
131         config.run_lib_path = path.clone();
132         config.compile_lib_path = path;
133     }
134     let current_exe_path = env::current_exe().unwrap();
135     let deps_path = current_exe_path.parent().unwrap();
136     let profile_path = deps_path.parent().unwrap();
137
138     // Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
139     // This is valuable because a) it allows us to monitor what external dependencies are used
140     // and b) it ensures that conflicting rlibs are resolved properly.
141     let host_libs = option_env!("HOST_LIBS")
142         .map(|p| format!(" -L dependency={}", Path::new(p).join("deps").display()))
143         .unwrap_or_default();
144     config.target_rustcflags = Some(format!(
145         "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}",
146         deps_path.display(),
147         host_libs,
148         &*EXTERN_FLAGS,
149     ));
150
151     config.src_base = Path::new("tests").join(test_dir);
152     config.build_base = profile_path.join("test").join(test_dir);
153     config.rustc_path = profile_path.join(if cfg!(windows) {
154         "clippy-driver.exe"
155     } else {
156         "clippy-driver"
157     });
158     config
159 }
160
161 fn run_ui() {
162     let config = base_config("ui");
163     // use tests/clippy.toml
164     let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap());
165     compiletest::run_tests(&config);
166 }
167
168 fn run_internal_tests() {
169     // only run internal tests with the internal-tests feature
170     if !RUN_INTERNAL_TESTS {
171         return;
172     }
173     let config = base_config("ui-internal");
174     compiletest::run_tests(&config);
175 }
176
177 fn run_ui_toml() {
178     fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
179         let mut result = true;
180         let opts = compiletest::test_opts(config);
181         for dir in fs::read_dir(&config.src_base)? {
182             let dir = dir?;
183             if !dir.file_type()?.is_dir() {
184                 continue;
185             }
186             let dir_path = dir.path();
187             let _g = VarGuard::set("CARGO_MANIFEST_DIR", &dir_path);
188             for file in fs::read_dir(&dir_path)? {
189                 let file = file?;
190                 let file_path = file.path();
191                 if file.file_type()?.is_dir() {
192                     continue;
193                 }
194                 if file_path.extension() != Some(OsStr::new("rs")) {
195                     continue;
196                 }
197                 let paths = compiletest::common::TestPaths {
198                     file: file_path,
199                     base: config.src_base.clone(),
200                     relative_dir: dir_path.file_name().unwrap().into(),
201                 };
202                 let test_name = compiletest::make_test_name(config, &paths);
203                 let index = tests
204                     .iter()
205                     .position(|test| test.desc.name == test_name)
206                     .expect("The test should be in there");
207                 result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
208             }
209         }
210         Ok(result)
211     }
212
213     let mut config = base_config("ui-toml");
214     config.src_base = config.src_base.canonicalize().unwrap();
215
216     let tests = compiletest::make_tests(&config);
217
218     let res = run_tests(&config, tests);
219     match res {
220         Ok(true) => {},
221         Ok(false) => panic!("Some tests failed"),
222         Err(e) => {
223             panic!("I/O failure during tests: {:?}", e);
224         },
225     }
226 }
227
228 fn run_ui_cargo() {
229     fn run_tests(
230         config: &compiletest::Config,
231         filters: &[String],
232         mut tests: Vec<tester::TestDescAndFn>,
233     ) -> Result<bool, io::Error> {
234         let mut result = true;
235         let opts = compiletest::test_opts(config);
236
237         for dir in fs::read_dir(&config.src_base)? {
238             let dir = dir?;
239             if !dir.file_type()?.is_dir() {
240                 continue;
241             }
242
243             // Use the filter if provided
244             let dir_path = dir.path();
245             for filter in filters {
246                 if !dir_path.ends_with(filter) {
247                     continue;
248                 }
249             }
250
251             for case in fs::read_dir(&dir_path)? {
252                 let case = case?;
253                 if !case.file_type()?.is_dir() {
254                     continue;
255                 }
256
257                 let src_path = case.path().join("src");
258
259                 // When switching between branches, if the previous branch had a test
260                 // that the current branch does not have, the directory is not removed
261                 // because an ignored Cargo.lock file exists.
262                 if !src_path.exists() {
263                     continue;
264                 }
265
266                 env::set_current_dir(&src_path)?;
267                 for file in fs::read_dir(&src_path)? {
268                     let file = file?;
269                     if file.file_type()?.is_dir() {
270                         continue;
271                     }
272
273                     // Search for the main file to avoid running a test for each file in the project
274                     let file_path = file.path();
275                     match file_path.file_name().and_then(OsStr::to_str) {
276                         Some("main.rs") => {},
277                         _ => continue,
278                     }
279                     let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
280                     let paths = compiletest::common::TestPaths {
281                         file: file_path,
282                         base: config.src_base.clone(),
283                         relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
284                     };
285                     let test_name = compiletest::make_test_name(config, &paths);
286                     let index = tests
287                         .iter()
288                         .position(|test| test.desc.name == test_name)
289                         .expect("The test should be in there");
290                     result &= tester::run_tests_console(&opts, vec![tests.swap_remove(index)])?;
291                 }
292             }
293         }
294         Ok(result)
295     }
296
297     if IS_RUSTC_TEST_SUITE {
298         return;
299     }
300
301     let mut config = base_config("ui-cargo");
302     config.src_base = config.src_base.canonicalize().unwrap();
303
304     let tests = compiletest::make_tests(&config);
305
306     let current_dir = env::current_dir().unwrap();
307     let res = run_tests(&config, &config.filters, tests);
308     env::set_current_dir(current_dir).unwrap();
309
310     match res {
311         Ok(true) => {},
312         Ok(false) => panic!("Some tests failed"),
313         Err(e) => {
314             panic!("I/O failure during tests: {:?}", e);
315         },
316     }
317 }
318
319 #[test]
320 fn compile_test() {
321     set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
322     run_ui();
323     run_ui_toml();
324     run_ui_cargo();
325     run_internal_tests();
326 }
327
328 /// Restores an env var on drop
329 #[must_use]
330 struct VarGuard {
331     key: &'static str,
332     value: Option<OsString>,
333 }
334
335 impl VarGuard {
336     fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
337         let value = var_os(key);
338         set_var(key, val);
339         Self { key, value }
340     }
341 }
342
343 impl Drop for VarGuard {
344     fn drop(&mut self) {
345         match self.value.as_deref() {
346             None => remove_var(self.key),
347             Some(value) => set_var(self.key, value),
348         }
349     }
350 }