rustemo_compiler/
utils.rs

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
#[cfg(test)]
pub fn type_of<T>(_: &T) -> &'static str {
    std::any::type_name::<T>()
}

/// Returns a first difference between two strings. Used by `output_cmp!` macro.
pub fn string_difference(a: &str, b: &str) -> Option<(usize, (char, char))> {
    a.chars()
        .zip(b.chars())
        .enumerate()
        .find(|(_, (a, b))| a != b)
        .or_else(|| match a.len().cmp(&b.len()) {
            std::cmp::Ordering::Less => {
                Some((a.len(), (' ', b[a.len()..].chars().next().unwrap())))
            }
            std::cmp::Ordering::Greater => {
                Some((b.len(), (' ', a[b.len()..].chars().next().unwrap())))
            }
            std::cmp::Ordering::Equal => None,
        })
}

/// Used in tests for storing and comparing string representations in files.
///
/// # Example
///
/// ```rust
/// let states = lr_states_for_grammar(&grammar, &settings);
/// output_cmp!("grammar.expected.txt", format!("{states:#?}"));
/// ```
///
/// If the file `grammar.expected.txt` exists its content will be compared
/// to the string of the second parameter. If the file doesn't exist it will
/// be created with the content of the second parameter. The idea is to
/// check manually the content first time it is created and commit to git repo.
/// Whenever the file is changed the assert will fail. In that case we delete
/// output file, run the test to produce the new version and investigate the
/// diff with git. If everything is expected we commit the new version of the
/// output file.
///
/// This is helpful for testing the content of larger structures.
#[macro_export]
macro_rules! output_cmp {
    ($path:expr, $out_str:expr) => {{
        use {
            std::{fs, path::PathBuf},
            $crate::utils::string_difference,
        };
        let t_path: PathBuf =
            [env!("CARGO_MANIFEST_DIR"), $path].iter().collect();

        if t_path.exists() {
            let content: String =
                fs::read_to_string(&t_path).unwrap_or_else(|err| {
                    panic!("Cannot load output file {:?}: {}", t_path, err)
                });
            if let Some(diff) = string_difference(&content, &$out_str) {
                assert!(false, "Strings differ at: {:?}", diff)
            }
        } else {
            fs::write(&t_path, $out_str).unwrap_or_else(|err| {
                panic!("Error writing file {:?}: {}", t_path, err)
            });
        }
    }};
}
pub use output_cmp;

/// Used in tests to calculate local file path relative to the source file.
/// Requires call to `file!()` as a first parameter.
///
/// # Example
/// ```rust
/// MyParser::parse_file(local_file!(file!(), "my_local_file.txt"));
/// ```
#[macro_export]
macro_rules! local_file {
    ($this:expr, $local_path:expr) => {
        &std::path::PathBuf::from(
            std::env::var("CARGO_WORKSPACE_DIR").unwrap_or(".".to_string()),
        )
        .join($this)
        .with_file_name($local_path)
    };
}
pub use local_file;