rustemo_compiler/
utils.rs

1#[cfg(test)]
2pub fn type_of<T>(_: &T) -> &'static str {
3    std::any::type_name::<T>()
4}
5
6/// Returns a first difference between two strings. Used by `output_cmp!` macro.
7pub fn string_difference(a: &str, b: &str) -> Option<(usize, (char, char))> {
8    a.chars()
9        .zip(b.chars())
10        .enumerate()
11        .find(|(_, (a, b))| a != b)
12        .or_else(|| match a.len().cmp(&b.len()) {
13            std::cmp::Ordering::Less => {
14                Some((a.len(), (' ', b[a.len()..].chars().next().unwrap())))
15            }
16            std::cmp::Ordering::Greater => {
17                Some((b.len(), (' ', a[b.len()..].chars().next().unwrap())))
18            }
19            std::cmp::Ordering::Equal => None,
20        })
21}
22
23/// Used in tests for storing and comparing string representations in files.
24///
25/// # Example
26///
27/// ```rust
28/// let states = lr_states_for_grammar(&grammar, &settings);
29/// output_cmp!("grammar.expected.txt", format!("{states:#?}"));
30/// ```
31///
32/// If the file `grammar.expected.txt` exists its content will be compared
33/// to the string of the second parameter. If the file doesn't exist it will
34/// be created with the content of the second parameter. The idea is to
35/// check manually the content first time it is created and commit to git repo.
36/// Whenever the file is changed the assert will fail. In that case we delete
37/// output file, run the test to produce the new version and investigate the
38/// diff with git. If everything is expected we commit the new version of the
39/// output file.
40///
41/// This is helpful for testing the content of larger structures.
42#[macro_export]
43macro_rules! output_cmp {
44    ($path:expr, $out_str:expr) => {{
45        use {
46            std::{fs, path::PathBuf},
47            $crate::utils::string_difference,
48        };
49        let t_path: PathBuf = [env!("CARGO_MANIFEST_DIR"), $path].iter().collect();
50
51        if t_path.exists() {
52            let content: String = fs::read_to_string(&t_path)
53                .unwrap_or_else(|err| panic!("Cannot load output file {:?}: {}", t_path, err));
54            if let Some(diff) = string_difference(&content, &$out_str) {
55                assert!(false, "Strings differ at: {:?}", diff)
56            }
57        } else {
58            fs::write(&t_path, $out_str)
59                .unwrap_or_else(|err| panic!("Error writing file {:?}: {}", t_path, err));
60        }
61    }};
62}
63pub use output_cmp;
64
65/// Used in tests to calculate local file path relative to the source file.
66/// Requires call to `file!()` as a first parameter.
67///
68/// # Example
69/// ```rust
70/// MyParser::parse_file(local_file!(file!(), "my_local_file.txt"));
71/// ```
72#[macro_export]
73macro_rules! local_file {
74    ($this:expr, $local_path:expr) => {
75        &std::path::PathBuf::from(std::env::var("CARGO_WORKSPACE_DIR").unwrap_or(".".to_string()))
76            .join($this)
77            .with_file_name($local_path)
78    };
79}
80pub use local_file;