]> git.lizzy.rs Git - rust.git/blob - tests/compile-test.rs
Update deploy CI
[rust.git] / tests / compile-test.rs
1 #![feature(test)] // compiletest_rs requires this attribute
2 #![feature(once_cell)]
3 #![feature(try_blocks)]
4
5 use compiletest_rs as compiletest;
6 use compiletest_rs::common::Mode as TestMode;
7
8 use std::env::{self, remove_var, set_var, var_os};
9 use std::ffi::{OsStr, OsString};
10 use std::fs;
11 use std::io;
12 use std::path::{Path, PathBuf};
13
14 mod cargo;
15
16 // whether to run internal tests or not
17 const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
18
19 fn host_lib() -> PathBuf {
20     option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
21 }
22
23 fn clippy_driver_path() -> PathBuf {
24     option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
25 }
26
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.
32 //
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] = &[
39         "clippy_lints",
40         "clippy_utils",
41         "if_chain",
42         "quote",
43         "regex",
44         "serde",
45         "serde_derive",
46         "syn",
47     ];
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)?
57         } {
58             flags += &format!(" --extern {}={}", name, path.display());
59             crates.entry(name).or_default().push(path.clone());
60         }
61     }
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
66         let paths = crates
67             .into_values()
68             .flatten()
69             .map(|p| strip_current_dir(&p).display().to_string())
70             .collect::<Vec<_>>()
71             .join(" \\\n");
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"
78         } else {
79             "running `cargo clean`"
80         };
81
82         panic!(
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
89         );
90     }
91     flags
92 }
93
94 fn default_config() -> compiletest::Config {
95     let mut config = compiletest::Config::default();
96
97     if let Ok(filters) = env::var("TESTNAME") {
98         config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
99     }
100
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;
105     }
106
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(),
112     ));
113
114     config.build_base = host_lib().join("test_build_base");
115     config.rustc_path = clippy_driver_path();
116     config
117 }
118
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);
125 }
126
127 fn run_internal_tests(cfg: &mut compiletest::Config) {
128     // only run internal tests with the internal-tests feature
129     if !RUN_INTERNAL_TESTS {
130         return;
131     }
132     cfg.mode = TestMode::Ui;
133     cfg.src_base = Path::new("tests").join("ui-internal");
134     compiletest::run_tests(cfg);
135 }
136
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)? {
142             let dir = dir?;
143             if !dir.file_type()?.is_dir() {
144                 continue;
145             }
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)? {
149                 let file = file?;
150                 let file_path = file.path();
151                 if file.file_type()?.is_dir() {
152                     continue;
153                 }
154                 if file_path.extension() != Some(OsStr::new("rs")) {
155                     continue;
156                 }
157                 let paths = compiletest::common::TestPaths {
158                     file: file_path,
159                     base: config.src_base.clone(),
160                     relative_dir: dir_path.file_name().unwrap().into(),
161                 };
162                 let test_name = compiletest::make_test_name(config, &paths);
163                 let index = tests
164                     .iter()
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)])?;
168             }
169         }
170         Ok(result)
171     }
172
173     config.mode = TestMode::Ui;
174     config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap();
175
176     let tests = compiletest::make_tests(config);
177
178     let res = run_tests(config, tests);
179     match res {
180         Ok(true) => {},
181         Ok(false) => panic!("Some tests failed"),
182         Err(e) => {
183             panic!("I/O failure during tests: {:?}", e);
184         },
185     }
186 }
187
188 fn run_ui_cargo(config: &mut compiletest::Config) {
189     fn run_tests(
190         config: &compiletest::Config,
191         filters: &[String],
192         mut tests: Vec<tester::TestDescAndFn>,
193     ) -> Result<bool, io::Error> {
194         let mut result = true;
195         let opts = compiletest::test_opts(config);
196
197         for dir in fs::read_dir(&config.src_base)? {
198             let dir = dir?;
199             if !dir.file_type()?.is_dir() {
200                 continue;
201             }
202
203             // Use the filter if provided
204             let dir_path = dir.path();
205             for filter in filters {
206                 if !dir_path.ends_with(filter) {
207                     continue;
208                 }
209             }
210
211             for case in fs::read_dir(&dir_path)? {
212                 let case = case?;
213                 if !case.file_type()?.is_dir() {
214                     continue;
215                 }
216
217                 let src_path = case.path().join("src");
218
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() {
223                     continue;
224                 }
225
226                 env::set_current_dir(&src_path)?;
227                 for file in fs::read_dir(&src_path)? {
228                     let file = file?;
229                     if file.file_type()?.is_dir() {
230                         continue;
231                     }
232
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") => {},
237                         _ => continue,
238                     }
239                     let _g = VarGuard::set("CLIPPY_CONF_DIR", case.path());
240                     let paths = compiletest::common::TestPaths {
241                         file: file_path,
242                         base: config.src_base.clone(),
243                         relative_dir: src_path.strip_prefix(&config.src_base).unwrap().into(),
244                     };
245                     let test_name = compiletest::make_test_name(config, &paths);
246                     let index = tests
247                         .iter()
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)])?;
251                 }
252             }
253         }
254         Ok(result)
255     }
256
257     if cargo::is_rustc_test_suite() {
258         return;
259     }
260
261     config.mode = TestMode::Ui;
262     config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
263
264     let tests = compiletest::make_tests(config);
265
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();
269
270     match res {
271         Ok(true) => {},
272         Ok(false) => panic!("Some tests failed"),
273         Err(e) => {
274             panic!("I/O failure during tests: {:?}", e);
275         },
276     }
277 }
278
279 fn prepare_env() {
280     set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
281     set_var("__CLIPPY_INTERNAL_TESTS", "true");
282     //set_var("RUST_BACKTRACE", "0");
283 }
284
285 #[test]
286 fn compile_test() {
287     prepare_env();
288     let mut config = default_config();
289     run_ui(&mut config);
290     run_ui_toml(&mut config);
291     run_ui_cargo(&mut config);
292     run_internal_tests(&mut config);
293 }
294
295 /// Restores an env var on drop
296 #[must_use]
297 struct VarGuard {
298     key: &'static str,
299     value: Option<OsString>,
300 }
301
302 impl VarGuard {
303     fn set(key: &'static str, val: impl AsRef<OsStr>) -> Self {
304         let value = var_os(key);
305         set_var(key, val);
306         Self { key, value }
307     }
308 }
309
310 impl Drop for VarGuard {
311     fn drop(&mut self) {
312         match self.value.as_deref() {
313             None => remove_var(self.key),
314             Some(value) => set_var(self.key, value),
315         }
316     }
317 }
318
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) {
322             return stripped;
323         }
324     }
325     path
326 }