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;