1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Testing utilities

use std::collections::HashMap;
use std::path::Path;
use std::{fs, path::PathBuf};

use similar_asserts::assert_eq;

/// Key for differentiating between kinds of test outputs
pub trait PathKey: std::fmt::Display + PartialEq + Eq + std::hash::Hash + Clone + Sized {
    /// Return the list of all possible test output kinds
    fn all() -> &'static [Self];
    /// Return the directory name for the local results
    fn local_results_prefix() -> &'static str {
        "localRsResults"
    }
    /// Return the directory name for the public results
    fn public_results_prefix() -> &'static str {
        "rsResults"
    }
}

/// Test output manager
pub struct Paths<K: PathKey> {
    /// Local (generated) test results
    local_results: PathBuf,
    /// Valid (bumped) test results
    public_results: PathBuf,
    /// Computed file names
    paths: HashMap<K, PathBuf>,
}

impl<K: PathKey + 'static> Paths<K> {
    /// Create a new output manager for the given input path
    ///
    /// # Parameters
    ///
    /// * `input_path`: input file for the current test case
    pub fn new(input_path: &Path) -> std::io::Result<Self> {
        // Create the results directory
        let dir_name = input_path.parent().unwrap();

        let local_results = dir_name.join(K::local_results_prefix());
        let public_results = dir_name.join(K::public_results_prefix());

        for result in &[&local_results, &public_results] {
            fs::create_dir_all(result)?;
        }

        let file_name = input_path
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string();

        let paths: HashMap<_, _> = K::all()
            .iter()
            .map(|key| (key.clone(), PathBuf::from(format!("{}.{}", file_name, key))))
            .collect();

        // Pre-run cleanup
        for path in paths.values() {
            let path = local_results.join(path);
            if path.exists() {
                fs::remove_file(path).expect("failed to cleanup result path");
            }
        }

        Ok(Self {
            local_results,
            public_results,
            paths,
        })
    }

    /// Obtain a path for a given output kind
    pub fn path(&self, key: K) -> PathBuf {
        self.local_results.join(self.paths.get(&key).unwrap())
    }

    /// Complete the testing process for this test case
    ///
    /// This will check the local result against the public results to find discrepancies, or
    /// bump the local results if requested.
    ///
    /// To bump results, set LANG_UTIL_TEST=bump before running cargo test.
    pub fn finish(self) {
        #[derive(PartialEq, Eq)]
        enum Mode {
            Check,
            Bump,
        }

        let mode = std::env::var("LANG_UTIL_TEST")
            .ok()
            .map(|value| {
                if value == "bump" {
                    Mode::Bump
                } else {
                    Mode::Check
                }
            })
            .unwrap_or(Mode::Check);

        for (_k, name) in self.paths {
            // Compute full result paths
            let local_result = self.local_results.join(&name);
            let public_result = self.public_results.join(&name);

            if local_result.exists() {
                match mode {
                    Mode::Check => {
                        assert!(public_result.exists(), "missing snapshot");

                        let local = std::fs::read_to_string(&local_result)
                            .expect("failed to read local result");
                        let public = std::fs::read_to_string(&public_result)
                            .expect("failed to read snapshot");

                        assert_eq!(local, public, "snapshot mismatch");
                    }
                    Mode::Bump => {
                        std::fs::copy(local_result, public_result)
                            .expect("failed to bump result to snapshot");
                    }
                }
            } else {
                assert!(!public_result.exists(), "missing local result");
            }
        }
    }
}