ef_tests/
suite.rs

1//! Abstractions for groups of tests.
2
3use crate::{
4    case::{Case, Cases},
5    result::assert_tests_pass,
6};
7use std::path::{Path, PathBuf};
8use walkdir::{DirEntry, WalkDir};
9
10/// A collection of tests.
11pub trait Suite {
12    /// The type of test cases in this suite.
13    type Case: Case;
14
15    /// The path to the test suite directory.
16    fn suite_path(&self) -> &Path;
17
18    /// Run all test cases in the suite.
19    fn run(&self) {
20        let suite_path = self.suite_path();
21        for entry in WalkDir::new(suite_path).min_depth(1).max_depth(1) {
22            let entry = entry.expect("Failed to read directory");
23            if entry.file_type().is_dir() {
24                self.run_only(entry.file_name().to_string_lossy().as_ref());
25            }
26        }
27    }
28
29    /// Load and run each contained test case for the provided sub-folder.
30    ///
31    /// # Note
32    ///
33    /// This recursively finds every test description in the resulting path.
34    fn run_only(&self, name: &str) {
35        // Build the path to the test suite directory
36        let suite_path = self.suite_path().join(name);
37
38        // Verify that the path exists
39        assert!(suite_path.exists(), "Test suite path does not exist: {suite_path:?}");
40
41        // Find all files with the ".json" extension in the test suite directory
42        let test_cases = find_all_files_with_extension(&suite_path, ".json")
43            .into_iter()
44            .map(|test_case_path| {
45                let case = Self::Case::load(&test_case_path).expect("test case should load");
46                (test_case_path, case)
47            })
48            .collect();
49
50        // Run the test cases and collect the results
51        let results = Cases { test_cases }.run();
52
53        // Assert that all tests in the suite pass
54        assert_tests_pass(name, &suite_path, &results);
55    }
56}
57
58/// Recursively find all files with a given extension.
59fn find_all_files_with_extension(path: &Path, extension: &str) -> Vec<PathBuf> {
60    WalkDir::new(path)
61        .into_iter()
62        .filter_map(Result::ok)
63        .filter(|e| e.file_name().to_string_lossy().ends_with(extension))
64        .map(DirEntry::into_path)
65        .collect()
66}