rustpython_unparser/
lib.rs1pub 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 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}