1 #![feature(test)] // compiletest_rs requires this attribute
4 #![cfg_attr(feature = "deny-warnings", deny(warnings))]
5 #![warn(rust_2018_idioms, unused_lifetimes)]
6 #![allow(unused_extern_crates)]
8 use compiletest
::{status_emitter, CommandBuilder, OutputConflictHandling}
;
9 use ui_test
as compiletest
;
10 use ui_test
::Mode
as TestMode
;
12 use std
::collections
::BTreeMap
;
13 use std
::env
::{self, remove_var, set_var, var_os}
;
14 use std
::ffi
::{OsStr, OsString}
;
16 use std
::path
::{Path, PathBuf}
;
17 use std
::sync
::LazyLock
;
18 use test_utils
::IS_RUSTC_TEST_SUITE
;
20 // Test dependencies may need an `extern crate` here to ensure that they show up
21 // in the depinfo file (otherwise cargo thinks they are unused)
22 extern crate clippy_lints
;
23 extern crate clippy_utils
;
24 extern crate derive_new
;
26 extern crate if_chain
;
27 extern crate itertools
;
28 extern crate parking_lot
;
33 /// All crates used in UI tests are listed here
34 static TEST_DEPENDENCIES
: &[&str] = &[
50 /// Produces a string with an `--extern` flag for all UI test crate
53 /// The dependency files are located by parsing the depinfo file for this test
54 /// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
55 /// dependencies must be added to Cargo.toml at the project root. Test
56 /// dependencies that are not *directly* used by this test module require an
57 /// `extern crate` declaration.
58 static EXTERN_FLAGS
: LazyLock
<Vec
<String
>> = LazyLock
::new(|| {
59 let current_exe_depinfo
= {
60 let mut path
= env
::current_exe().unwrap();
61 path
.set_extension("d");
62 fs
::read_to_string(path
).unwrap()
64 let mut crates
= BTreeMap
::<&str, &str>::new();
65 for line
in current_exe_depinfo
.lines() {
66 // each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
67 let parse_name_path
= || {
68 if line
.starts_with(char::is_whitespace
) {
71 let path_str
= line
.strip_suffix('
:'
)?
;
72 let path
= Path
::new(path_str
);
73 if !matches
!(path
.extension()?
.to_str()?
, "rlib" | "so" | "dylib" | "dll") {
76 let (name
, _hash
) = path
.file_stem()?
.to_str()?
.rsplit_once('
-'
)?
;
77 // the "lib" prefix is not present for dll files
78 let name
= name
.strip_prefix("lib").unwrap_or(name
);
79 Some((name
, path_str
))
81 if let Some((name
, path
)) = parse_name_path() {
82 if TEST_DEPENDENCIES
.contains(&name
) {
83 // A dependency may be listed twice if it is available in sysroot,
84 // and the sysroot dependencies are listed first. As of the writing,
85 // this only seems to apply to if_chain.
86 crates
.insert(name
, path
);
90 let not_found
: Vec
<&str> = TEST_DEPENDENCIES
93 .filter(|n
| !crates
.contains_key(n
))
97 "dependencies not found in depinfo: {not_found:?}\n\
98 help: Make sure the `-Z binary-dep-depinfo` rust flag is enabled\n\
99 help: Try adding to dev-dependencies in Cargo.toml\n\
100 help: Be sure to also add `extern crate ...;` to tests/compile-test.rs",
104 .map(|(name
, path
)| format
!("--extern={name}={path}"))
110 // whether to run internal tests or not
111 const RUN_INTERNAL_TESTS
: bool
= cfg
!(feature
= "internal");
113 fn base_config(test_dir
: &str) -> compiletest
::Config
{
114 let mut config
= compiletest
::Config
{
115 mode
: TestMode
::Yolo
,
116 stderr_filters
: vec
![],
117 stdout_filters
: vec
![],
118 output_conflict_handling
: if var_os("RUSTC_BLESS").is_some_and(|v
| v
!= "0")
119 || env
::args().any(|arg
| arg
== "--bless")
121 OutputConflictHandling
::Bless
123 OutputConflictHandling
::Error("cargo uibless".into())
126 out_dir
: PathBuf
::from(std
::env
::var_os("CARGO_TARGET_DIR").unwrap_or("target".into())).join("ui_test"),
127 ..compiletest
::Config
::rustc(Path
::new("tests").join(test_dir
))
130 if let Some(_path
) = option_env
!("RUSTC_LIB_PATH") {
131 //let path = PathBuf::from(path);
132 //config.run_lib_path = path.clone();
133 //config.compile_lib_path = path;
135 let current_exe_path
= env
::current_exe().unwrap();
136 let deps_path
= current_exe_path
.parent().unwrap();
137 let profile_path
= deps_path
.parent().unwrap();
139 config
.program
.args
.extend(
143 "-Ainternal_features",
146 &format
!("-Ldependency={}", deps_path
.display()),
148 .map(OsString
::from
),
151 config
.program
.args
.extend(EXTERN_FLAGS
.iter().map(OsString
::from
));
153 if let Some(host_libs
) = option_env
!("HOST_LIBS") {
154 let dep
= format
!("-Ldependency={}", Path
::new(host_libs
).join("deps").display());
155 config
.program
.args
.push(dep
.into());
158 // Normalize away slashes in windows paths.
159 config
.stderr_filter(r
"\\", "/");
161 //config.build_base = profile_path.join("test").join(test_dir);
162 config
.program
.program
= profile_path
.join(if cfg
!(windows
) {
170 fn test_filter() -> Box
<dyn Sync
+ Fn(&Path
) -> bool
> {
171 if let Ok(filters
) = env
::var("TESTNAME") {
172 let filters
: Vec
<_
> = filters
.split('
,'
).map(ToString
::to_string
).collect();
173 Box
::new(move |path
| filters
.iter().any(|f
| path
.to_string_lossy().contains(f
)))
180 let config
= base_config("ui");
181 //config.rustfix_coverage = true;
182 // use tests/clippy.toml
183 let _g
= VarGuard
::set("CARGO_MANIFEST_DIR", fs
::canonicalize("tests").unwrap());
184 let _threads
= VarGuard
::set(
186 // if RUST_TEST_THREADS is set, adhere to it, otherwise override it
187 env
::var("RUST_TEST_THREADS").unwrap_or_else(|_
| {
188 std
::thread
::available_parallelism()
189 .map_or(1, std
::num
::NonZeroUsize
::get
)
194 let test_filter
= test_filter();
196 compiletest
::run_tests_generic(
198 move |path
| compiletest
::default_file_filter(path
) && test_filter(path
),
199 compiletest
::default_per_file_config
,
200 status_emitter
::Text
,
203 check_rustfix_coverage();
206 fn run_internal_tests() {
207 // only run internal tests with the internal-tests feature
208 if !RUN_INTERNAL_TESTS
{
211 let mut config
= base_config("ui-internal");
212 if let OutputConflictHandling
::Error(err
) = &mut config
.output_conflict_handling
{
213 *err
= "cargo uitest --features internal -- -- --bless".into();
215 let test_filter
= test_filter();
217 compiletest
::run_tests_generic(
219 move |path
| compiletest
::default_file_filter(path
) && test_filter(path
),
220 compiletest
::default_per_file_config
,
221 status_emitter
::Text
,
227 let mut config
= base_config("ui-toml");
229 config
.stderr_filter(
231 &fs
::canonicalize("tests")
242 let test_filter
= test_filter();
244 ui_test
::run_tests_generic(
246 |path
| compiletest
::default_file_filter(path
) && test_filter(path
),
248 let mut config
= config
.clone();
252 .push(("CLIPPY_CONF_DIR".into(), Some(path
.parent().unwrap().into())));
255 status_emitter
::Text
,
261 if IS_RUSTC_TEST_SUITE
{
265 let mut config
= base_config("ui-cargo");
266 config
.program
.input_file_flag
= CommandBuilder
::cargo().input_file_flag
;
267 config
.program
.out_dir_flag
= CommandBuilder
::cargo().out_dir_flag
;
268 config
.program
.args
= vec
!["clippy".into(), "--color".into(), "never".into(), "--quiet".into()];
272 .push(("RUSTFLAGS".into(), Some("-Dwarnings".into())));
273 // We need to do this while we still have a rustc in the `program` field.
274 config
.fill_host_and_target().unwrap();
275 config
.dependencies_crate_manifest_path
= None
;
276 config
.program
.program
.set_file_name(if cfg
!(windows
) {
281 config
.edition
= None
;
283 config
.stderr_filter(
285 &fs
::canonicalize("tests")
296 let test_filter
= test_filter();
298 ui_test
::run_tests_generic(
300 |path
| test_filter(path
) && path
.ends_with("Cargo.toml"),
302 let mut config
= config
.clone();
303 config
.out_dir
= PathBuf
::from("target/ui_test_cargo/").join(path
.parent().unwrap());
306 status_emitter
::Text
,
312 // Support being run by cargo nextest - https://nexte.st/book/custom-test-harnesses.html
313 if env
::args().any(|arg
| arg
== "--list") {
314 if !env
::args().any(|arg
| arg
== "--ignored") {
315 println
!("compile_test: test");
321 set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
322 // The SPEEDTEST_* env variables can be used to check Clippy's performance on your PR. It runs the
323 // affected test 1000 times and gets the average.
324 if let Ok(speedtest
) = std
::env
::var("SPEEDTEST") {
325 println
!("----------- STARTING SPEEDTEST -----------");
326 let f
= match speedtest
.as_str() {
327 "ui" => run_ui
as fn(),
328 "cargo" => run_ui_cargo
as fn(),
329 "toml" => run_ui_toml
as fn(),
330 "internal" => run_internal_tests
as fn(),
331 "rustfix-coverage-known-exceptions-accuracy" => rustfix_coverage_known_exceptions_accuracy
as fn(),
332 "ui-cargo-toml-metadata" => ui_cargo_toml_metadata
as fn(),
334 _
=> panic
!("unknown speedtest: {speedtest} || accepted speedtests are: [ui, cargo, toml, internal]"),
338 if let Ok(iterations_str
) = std
::env
::var("SPEEDTEST_ITERATIONS") {
339 iterations
= iterations_str
341 .unwrap_or_else(|_
| panic
!("Couldn't parse `{iterations_str}`, please use a valid u64"));
347 for _
in 0..iterations
{
348 let start
= std
::time
::Instant
::now();
350 sum
+= start
.elapsed().as_millis();
352 println
!("average {} time: {} millis.", speedtest
.to_uppercase(), sum
/ 1000);
357 run_internal_tests();
358 rustfix_coverage_known_exceptions_accuracy();
359 ui_cargo_toml_metadata();
363 const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS
: &[&str] = &[
365 "borrow_deref_ref_unfixable.rs",
366 "cast_size_32bit.rs",
368 "cmp_owned/without_suggestion.rs",
370 "deref_addrof_double_trigger.rs",
371 "doc/unbalanced_ticks.rs",
372 "eprint_with_newline.rs",
373 "explicit_counter_loop.rs",
374 "iter_skip_next_unfixable.rs",
380 "mem_replace_macro.rs",
381 "needless_arbitrary_self_type_unfixable.rs",
382 "needless_borrow_pat.rs",
383 "needless_for_each_unfixable.rs",
384 "nonminimal_bool.rs",
386 "redundant_static_lifetimes_multiple.rs",
387 "ref_binding_to_reference.rs",
389 "result_map_unit_fn_unfixable.rs",
391 "single_component_path_imports_nested_first.rs",
393 "suspicious_to_owned.rs",
394 "toplevel_ref_arg_non_rustfix.rs",
396 "unnecessary_clone.rs",
397 "unnecessary_lazy_eval_unfixable.rs",
399 "write_literal_2.rs",
402 fn check_rustfix_coverage() {
403 let missing_coverage_path
= Path
::new("debug/test/ui/rustfix_missing_coverage.txt");
404 let missing_coverage_path
= if let Ok(target_dir
) = std
::env
::var("CARGO_TARGET_DIR") {
405 PathBuf
::from(target_dir
).join(missing_coverage_path
)
407 missing_coverage_path
.to_path_buf()
410 if let Ok(missing_coverage_contents
) = std
::fs
::read_to_string(missing_coverage_path
) {
411 assert
!(RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS
.iter().is_sorted_by_key(Path
::new
));
413 for rs_file
in missing_coverage_contents
.lines() {
414 let rs_path
= Path
::new(rs_file
);
415 if rs_path
.starts_with("tests/ui/crashes") {
418 assert
!(rs_path
.starts_with("tests/ui/"), "{rs_file:?}");
419 let filename
= rs_path
.strip_prefix("tests/ui/").unwrap();
421 RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS
422 .binary_search_by_key(&filename
, Path
::new
)
424 "`{rs_file}` runs `MachineApplicable` diagnostics but is missing a `run-rustfix` annotation. \
425 Please either add `//@run-rustfix` at the top of the file or add the file to \
426 `RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS` in `tests/compile-test.rs`.",
432 fn rustfix_coverage_known_exceptions_accuracy() {
433 for filename
in RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS
{
434 let rs_path
= Path
::new("tests/ui").join(filename
);
435 assert
!(rs_path
.exists(), "`{}` does not exist", rs_path
.display());
436 let fixed_path
= rs_path
.with_extension("fixed");
437 assert
!(!fixed_path
.exists(), "`{}` exists", fixed_path
.display());
441 fn ui_cargo_toml_metadata() {
442 let ui_cargo_path
= Path
::new("tests/ui-cargo");
443 let cargo_common_metadata_path
= ui_cargo_path
.join("cargo_common_metadata");
444 let publish_exceptions
=
445 ["fail_publish", "fail_publish_true", "pass_publish_empty"].map(|path
| cargo_common_metadata_path
.join(path
));
447 for entry
in walkdir
::WalkDir
::new(ui_cargo_path
) {
448 let entry
= entry
.unwrap();
449 let path
= entry
.path();
450 if path
.file_name() != Some(OsStr
::new("Cargo.toml")) {
454 let toml
= fs
::read_to_string(path
).unwrap().parse
::<toml
::Value
>().unwrap();
456 let package
= toml
.as_table().unwrap().get("package").unwrap().as_table().unwrap();
458 let name
= package
.get("name").unwrap().as_str().unwrap().replace('
-'
, "_");
463 .map(|component
| component
.as_os_str().to_string_lossy().replace('
-'
, "_"))
465 || path
.starts_with(&cargo_common_metadata_path
),
466 "{path:?} has incorrect package name"
469 let publish
= package
.get("publish").and_then(toml
::Value
::as_bool
).unwrap_or(true);
471 !publish
|| publish_exceptions
.contains(&path
.parent().unwrap().to_path_buf()),
472 "{path:?} lacks `publish = false`"
477 /// Restores an env var on drop
481 value
: Option
<OsString
>,
485 fn set(key
: &'
static str, val
: impl AsRef
<OsStr
>) -> Self {
486 let value
= var_os(key
);
492 impl Drop
for VarGuard
{
494 match self.value
.as_deref() {
495 None
=> remove_var(self.key
),
496 Some(value
) => set_var(self.key
, value
),