blob: 74b6c4a3845a6bc7d34cff16918d3f4f830b9899 [file] [log] [blame] [edit]
//! Tidy check to ensure `#[test]` and `#[bench]` are not used directly inside
//! of the standard library.
//!
//! `core` and `alloc` cannot be tested directly due to duplicating lang items.
//! All tests and benchmarks must be written externally in
//! `{coretests,alloctests}/{tests,benches}`.
//!
//! Outside of the standard library, tests and benchmarks should be outlined
//! into separate files named `tests.rs` or `benches.rs`, or directories named
//! `tests` or `benches` unconfigured during normal build.
use std::path::Path;
use crate::diagnostics::{CheckId, TidyCtx};
use crate::walk::{filter_dirs, walk};
pub fn check(root_path: &Path, stdlib: bool, tidy_ctx: TidyCtx) {
let mut check = tidy_ctx.start_check(CheckId::new("unit_tests").path(root_path));
let skip = move |path: &Path, is_dir| {
let file_name = path.file_name().unwrap_or_default();
// Skip excluded directories and non-rust files
if is_dir {
if filter_dirs(path) || path.ends_with("src/doc") {
return true;
}
} else {
let extension = path.extension().unwrap_or_default();
if extension != "rs" {
return true;
}
}
// Tests in a separate package are always allowed
if is_dir && file_name != "tests" && file_name.as_encoded_bytes().ends_with(b"tests") {
return true;
}
if !stdlib {
// Outside of the standard library tests may also be in separate files in the same crate
if is_dir {
if file_name == "tests" || file_name == "benches" {
return true;
}
} else {
if file_name == "tests.rs" || file_name == "benches.rs" {
return true;
}
}
}
if is_dir {
// FIXME remove those exceptions once no longer necessary
file_name == "std_detect" || file_name == "std" || file_name == "test"
} else {
// Tests which use non-public internals and, as such, need to
// have the types in the same crate as the tests themselves. See
// the comment in alloctests/lib.rs.
path.ends_with("library/alloc/src/collections/btree/borrow/tests.rs")
|| path.ends_with("library/alloc/src/collections/btree/map/tests.rs")
|| path.ends_with("library/alloc/src/collections/btree/node/tests.rs")
|| path.ends_with("library/alloc/src/collections/btree/set/tests.rs")
|| path.ends_with("library/alloc/src/collections/linked_list/tests.rs")
|| path.ends_with("library/alloc/src/collections/vec_deque/tests.rs")
|| path.ends_with("library/alloc/src/raw_vec/tests.rs")
|| path.ends_with("library/alloc/src/wtf8/tests.rs")
}
};
walk(root_path, skip, &mut |entry, contents| {
let path = entry.path();
let package = path
.strip_prefix(root_path)
.unwrap()
.components()
.next()
.unwrap()
.as_os_str()
.to_str()
.unwrap();
for (i, line) in contents.lines().enumerate() {
let line = line.trim();
let is_test = || line.contains("#[test]") && !line.contains("`#[test]");
let is_bench = || line.contains("#[bench]") && !line.contains("`#[bench]");
if !line.starts_with("//") && (is_test() || is_bench()) {
let explanation = if stdlib {
format!(
"`{package}` unit tests and benchmarks must be placed into `{package}tests`"
)
} else {
"unit tests and benchmarks must be placed into \
separate files or directories named \
`tests.rs`, `benches.rs`, `tests` or `benches`"
.to_owned()
};
let name = if is_test() { "test" } else { "bench" };
check.error(format!(
"`{}:{}` contains `#[{name}]`; {explanation}",
path.display(),
i + 1,
));
return;
}
}
});
}