rustpython_unparser/
lib.rs

1pub mod unparser;
2mod utils;
3pub use crate::unparser::Unparser;
4#[cfg(feature = "transformer")]
5pub mod transformer;
6#[cfg(test)]
7mod tests {
8    use super::*;
9    use pretty_assertions::assert_eq;
10    use rustpython_ast::text_size::TextRange;
11    use rustpython_ast::Fold;
12    use rustpython_ast::TextSize;
13    use rustpython_parser::ast::Suite;
14    use rustpython_parser::Parse;
15
16    use std::fs;
17    use std::io;
18    use std::path::Path;
19    use std::process::Command;
20
21    struct RangesEraser {}
22
23    impl Fold<TextRange> for RangesEraser {
24        type TargetU = TextRange;
25
26        type Error = std::convert::Infallible;
27
28        type UserContext = TextRange;
29
30        fn will_map_user(&mut self, _user: &TextRange) -> Self::UserContext {
31            TextRange::new(TextSize::new(0), TextSize::new(0))
32        }
33
34        fn map_user(
35            &mut self,
36            _user: TextRange,
37            start: Self::UserContext,
38        ) -> Result<Self::TargetU, Self::Error> {
39            Ok(start)
40        }
41    }
42
43    fn run_tests_on_folders(source_folder: &str, results_folder: &str) -> io::Result<()> {
44        for entry in fs::read_dir(results_folder)? {
45            let entry = entry?;
46
47            let entry_path = entry.path();
48            if entry_path.is_file()
49                && entry_path.file_name().is_some_and(|name| {
50                    name.to_str()
51                        .is_some_and(|inner_name| inner_name.ends_with(".py"))
52                })
53            {
54                fs::remove_file(entry_path)?;
55            }
56        }
57
58        for entry in fs::read_dir(source_folder)? {
59            let entry = entry?;
60
61            let entry_path = entry.path();
62
63            if entry_path.is_file()
64                && entry_path.file_name().is_some_and(|name| {
65                    name.to_str()
66                        .is_some_and(|inner_name| inner_name.ends_with(".py"))
67                })
68            {
69                let file_content = fs::read_to_string(&entry_path)?;
70                let entry_path_str = entry_path.to_str().unwrap();
71                let mut unparser = Unparser::new();
72                let stmts = Suite::parse(&file_content, entry_path_str).unwrap();
73                for stmt in &stmts {
74                    unparser.unparse_stmt(stmt);
75                }
76                let new_source = unparser.source;
77                let old_file_name = entry_path.file_name().unwrap().to_str().unwrap();
78                let new_file_name = old_file_name.replace(".py", "_unparsed.py");
79                let new_entry_path_str = format!("{}/{}", results_folder, new_file_name);
80                let new_entry_path = Path::new(&new_entry_path_str);
81                fs::write(&new_entry_path, &new_source)?;
82                let new_stmts =
83                    Suite::parse(&new_source, new_entry_path.to_str().unwrap()).unwrap();
84                // erase range information
85                let mut eraser = RangesEraser {};
86                let mut erased_new_stmts = Vec::new();
87                for stmt in &new_stmts {
88                    erased_new_stmts.push(eraser.fold_stmt(stmt.to_owned()).unwrap());
89                }
90
91                let mut erased_stmts = Vec::new();
92                for stmt in &stmts {
93                    erased_stmts.push(eraser.fold_stmt(stmt.to_owned()).unwrap());
94                }
95
96                for (stmt, new_stmt) in erased_stmts.iter().zip(erased_new_stmts.iter()) {
97                    assert_eq!(stmt, new_stmt)
98                }
99            }
100        }
101        Ok(())
102    }
103
104    #[test]
105    fn test_predefined_files() -> io::Result<()> {
106        run_tests_on_folders("./test_files", "./test_files_unparsed")
107    }
108    #[test]
109    #[ignore = "Fuzzy tests are unstable and should only be used to explore new test cases"]
110    fn test_fuzzy_files() -> io::Result<()> {
111        let seed = rand::random::<usize>();
112
113        for i in 0..10 {
114            let file_name = format!("./fuzzy_test_files/fuzzy_test{}.py", i);
115
116            let file_content = Command::new(".venv/bin/python")
117                .arg("-m")
118                .arg("pysource_codegen")
119                .arg("--seed")
120                .arg(&seed.to_string())
121                .output()
122                .expect("failed to execute process");
123            fs::write(&file_name, &file_content.stdout)?;
124        }
125
126        run_tests_on_folders("./fuzzy_test_files", "./fuzzy_test_files_unparsed")?;
127
128        Ok(())
129    }
130}