tidy/
unit_tests.rs

1//! Tidy check to ensure `#[test]` and `#[bench]` are not used directly inside
2//! of the standard library.
3//!
4//! `core` and `alloc` cannot be tested directly due to duplicating lang items.
5//! All tests and benchmarks must be written externally in
6//! `{coretests,alloctests}/{tests,benches}`.
7//!
8//! Outside of the standard library, tests and benchmarks should be outlined
9//! into separate files named `tests.rs` or `benches.rs`, or directories named
10//! `tests` or `benches` unconfigured during normal build.
11
12use std::path::Path;
13
14use crate::walk::{filter_dirs, walk};
15
16pub fn check(root_path: &Path, stdlib: bool, bad: &mut bool) {
17    let skip = move |path: &Path, is_dir| {
18        let file_name = path.file_name().unwrap_or_default();
19
20        // Skip excluded directories and non-rust files
21        if is_dir {
22            if filter_dirs(path) || path.ends_with("src/doc") {
23                return true;
24            }
25        } else {
26            let extension = path.extension().unwrap_or_default();
27            if extension != "rs" {
28                return true;
29            }
30        }
31
32        // Tests in a separate package are always allowed
33        if is_dir && file_name != "tests" && file_name.as_encoded_bytes().ends_with(b"tests") {
34            return true;
35        }
36
37        if !stdlib {
38            // Outside of the standard library tests may also be in separate files in the same crate
39            if is_dir {
40                if file_name == "tests" || file_name == "benches" {
41                    return true;
42                }
43            } else {
44                if file_name == "tests.rs" || file_name == "benches.rs" {
45                    return true;
46                }
47            }
48        }
49
50        if is_dir {
51            // FIXME remove those exceptions once no longer necessary
52            file_name == "std_detect" || file_name == "std" || file_name == "test"
53        } else {
54            // Tests which use non-public internals and, as such, need to
55            // have the types in the same crate as the tests themselves. See
56            // the comment in alloctests/lib.rs.
57            path.ends_with("library/alloc/src/collections/btree/borrow/tests.rs")
58                || path.ends_with("library/alloc/src/collections/btree/map/tests.rs")
59                || path.ends_with("library/alloc/src/collections/btree/node/tests.rs")
60                || path.ends_with("library/alloc/src/collections/btree/set/tests.rs")
61                || path.ends_with("library/alloc/src/collections/linked_list/tests.rs")
62                || path.ends_with("library/alloc/src/collections/vec_deque/tests.rs")
63                || path.ends_with("library/alloc/src/raw_vec/tests.rs")
64                || path.ends_with("library/alloc/src/wtf8/tests.rs")
65        }
66    };
67
68    walk(root_path, skip, &mut |entry, contents| {
69        let path = entry.path();
70        let package = path
71            .strip_prefix(root_path)
72            .unwrap()
73            .components()
74            .next()
75            .unwrap()
76            .as_os_str()
77            .to_str()
78            .unwrap();
79        for (i, line) in contents.lines().enumerate() {
80            let line = line.trim();
81            let is_test = || line.contains("#[test]") && !line.contains("`#[test]");
82            let is_bench = || line.contains("#[bench]") && !line.contains("`#[bench]");
83            if !line.starts_with("//") && (is_test() || is_bench()) {
84                let explanation = if stdlib {
85                    format!(
86                        "`{package}` unit tests and benchmarks must be placed into `{package}tests`"
87                    )
88                } else {
89                    "unit tests and benchmarks must be placed into \
90                         separate files or directories named \
91                         `tests.rs`, `benches.rs`, `tests` or `benches`"
92                        .to_owned()
93                };
94                let name = if is_test() { "test" } else { "bench" };
95                tidy_error!(
96                    bad,
97                    "`{}:{}` contains `#[{}]`; {}",
98                    path.display(),
99                    i + 1,
100                    name,
101                    explanation,
102                );
103                return;
104            }
105        }
106    });
107}