litcheck_lit/test/registry/
default.rs

1use std::{path::Path, sync::Arc};
2
3use litcheck::{
4    diagnostics::{DiagResult, IntoDiagnostic},
5    fs,
6};
7
8use crate::test::{Test, TestConfig, TestList, TestSuite};
9
10use super::TestRegistry;
11
12/// The [DefaultTestRegistry] is used by default to locate tests for the builtin test formats.
13///
14/// It has the following behavior:
15///
16/// * Tests are discovered by traversing the contents of the test suite's `source_dir`,
17/// using the patterns in the [TestSuite] `patterns` field to determine if a file is
18/// a valid test
19/// * Tests are then selected/rejected based on the `filter`/`filter_out` fields
20/// of [Config], if unset, the default is to select all tests.
21/// * Hidden files are ignored
22pub struct DefaultTestRegistry;
23impl TestRegistry for DefaultTestRegistry {
24    fn all(&self, suite: Arc<TestSuite>) -> DiagResult<TestList> {
25        let source_dir = suite.source_dir();
26        get_file_based_tests(source_dir, suite.clone(), suite.config.clone())
27    }
28
29    fn find(
30        &self,
31        path_in_suite: &Path,
32        suite: Arc<TestSuite>,
33        config: Arc<TestConfig>,
34    ) -> DiagResult<TestList> {
35        let source_dir = suite.source_dir();
36        let absolute_path = source_dir.join(path_in_suite);
37        if absolute_path.is_dir() {
38            return get_file_based_tests(&absolute_path, suite, config);
39        }
40        log::debug!(
41            "checking if {} is a valid test file using default registry",
42            absolute_path.display()
43        );
44
45        let mut tests = TestList::default();
46        if is_file_based_test(&absolute_path, &suite, &config) {
47            log::debug!(
48                "{} is a valid test, adding it to registry",
49                absolute_path.display()
50            );
51            tests.push_back(Arc::new(Test::new(
52                path_in_suite.to_path_buf(),
53                suite,
54                config,
55            )));
56        }
57        Ok(tests)
58    }
59}
60
61/// Get all of the file-based tests for `suite` and `config`, starting from `search_path`
62fn get_file_based_tests(
63    search_path: &Path,
64    suite: Arc<TestSuite>,
65    config: Arc<TestConfig>,
66) -> DiagResult<TestList> {
67    debug_assert!(search_path.is_absolute());
68
69    log::debug!(
70        "searching for tests in {} using default registry",
71        search_path.display()
72    );
73    let results = fs::search_directory(search_path, true, |entry| {
74        let path = entry.path();
75        log::trace!("checking if file is valid test: {}", path.display());
76        is_file_based_test(path, &suite, &config)
77    });
78
79    let mut tests = TestList::default();
80    for result in results {
81        let test_path = result.into_diagnostic()?.into_path();
82        log::trace!("found test file: {}", test_path.display());
83        let relative_path = test_path
84            .strip_prefix(search_path)
85            .expect("expected path relative to search_path");
86        tests.push_back(Arc::new(Test::new(
87            relative_path.to_path_buf(),
88            suite.clone(),
89            config.clone(),
90        )));
91    }
92
93    Ok(tests)
94}
95
96/// Returns true if `path` matches the criteria for file-based tests belonging to `suite`
97fn is_file_based_test(path: &Path, suite: &TestSuite, config: &TestConfig) -> bool {
98    let is_hidden = path
99        .file_name()
100        .map(|name| name.as_encoded_bytes().starts_with(b"."))
101        .unwrap_or(false);
102
103    if is_hidden || !path.is_file() {
104        return false;
105    }
106
107    let path_in_suite = path.strip_prefix(suite.source_dir()).unwrap_or(path);
108    config
109        .patterns
110        .iter()
111        .any(|p| p.matches_path(path_in_suite))
112}