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