lang_util_dev/
test_util.rs

1//! Testing utilities
2
3use std::collections::HashMap;
4use std::path::Path;
5use std::{fs, path::PathBuf};
6
7use similar_asserts::assert_eq;
8
9/// Key for differentiating between kinds of test outputs
10pub trait PathKey: std::fmt::Display + PartialEq + Eq + std::hash::Hash + Clone + Sized {
11    /// Return the list of all possible test output kinds
12    fn all() -> &'static [Self];
13    /// Return the directory name for the local results
14    fn local_results_prefix() -> &'static str {
15        "localRsResults"
16    }
17    /// Return the directory name for the public results
18    fn public_results_prefix() -> &'static str {
19        "rsResults"
20    }
21}
22
23/// Test output manager
24pub struct Paths<K: PathKey> {
25    /// Local (generated) test results
26    local_results: PathBuf,
27    /// Valid (bumped) test results
28    public_results: PathBuf,
29    /// Computed file names
30    paths: HashMap<K, PathBuf>,
31}
32
33impl<K: PathKey + 'static> Paths<K> {
34    /// Create a new output manager for the given input path
35    ///
36    /// # Parameters
37    ///
38    /// * `input_path`: input file for the current test case
39    pub fn new(input_path: &Path) -> std::io::Result<Self> {
40        // Create the results directory
41        let dir_name = input_path.parent().unwrap();
42
43        let local_results = dir_name.join(K::local_results_prefix());
44        let public_results = dir_name.join(K::public_results_prefix());
45
46        for result in &[&local_results, &public_results] {
47            fs::create_dir_all(result)?;
48        }
49
50        let file_name = input_path
51            .file_name()
52            .unwrap()
53            .to_string_lossy()
54            .to_string();
55
56        let paths: HashMap<_, _> = K::all()
57            .iter()
58            .map(|key| (key.clone(), PathBuf::from(format!("{}.{}", file_name, key))))
59            .collect();
60
61        // Pre-run cleanup
62        for path in paths.values() {
63            let path = local_results.join(path);
64            if path.exists() {
65                fs::remove_file(path).expect("failed to cleanup result path");
66            }
67        }
68
69        Ok(Self {
70            local_results,
71            public_results,
72            paths,
73        })
74    }
75
76    /// Obtain a path for a given output kind
77    pub fn path(&self, key: K) -> PathBuf {
78        self.local_results.join(self.paths.get(&key).unwrap())
79    }
80
81    /// Complete the testing process for this test case
82    ///
83    /// This will check the local result against the public results to find discrepancies, or
84    /// bump the local results if requested.
85    ///
86    /// To bump results, set LANG_UTIL_TEST=bump before running cargo test.
87    pub fn finish(self) {
88        #[derive(PartialEq, Eq)]
89        enum Mode {
90            Check,
91            Bump,
92        }
93
94        let mode = std::env::var("LANG_UTIL_TEST")
95            .ok()
96            .map(|value| {
97                if value == "bump" {
98                    Mode::Bump
99                } else {
100                    Mode::Check
101                }
102            })
103            .unwrap_or(Mode::Check);
104
105        for (_k, name) in self.paths {
106            // Compute full result paths
107            let local_result = self.local_results.join(&name);
108            let public_result = self.public_results.join(&name);
109
110            if local_result.exists() {
111                match mode {
112                    Mode::Check => {
113                        assert!(public_result.exists(), "missing snapshot");
114
115                        let local = std::fs::read_to_string(&local_result)
116                            .expect("failed to read local result");
117                        let public = std::fs::read_to_string(&public_result)
118                            .expect("failed to read snapshot");
119
120                        assert_eq!(local, public, "snapshot mismatch");
121                    }
122                    Mode::Bump => {
123                        std::fs::copy(local_result, public_result)
124                            .expect("failed to bump result to snapshot");
125                    }
126                }
127            } else {
128                assert!(!public_result.exists(), "missing local result");
129            }
130        }
131    }
132}