Skip to content

Commit 2153b0f

Browse files
committed
add simple implementation for fuzzing
1 parent a542718 commit 2153b0f

File tree

12 files changed

+144
-11
lines changed

12 files changed

+144
-11
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
.pdm-python

.vscode/settings.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@
66
"editor.formatOnSave": true,
77
"[python]": {
88
"editor.defaultFormatter": "charliermarsh.ruff"
9-
}
9+
},
10+
"python.defaultInterpreterPath": ".venv/bin/python",
11+
"python.terminal.activateEnvInCurrentTerminal": true
1012
}

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ rustpython-ast = { version = "0.4.0" }
99
[dev-dependencies]
1010
rustpython-parser = "0.4.0"
1111
rustpython-ast = { version = "0.4.0", features = ["fold"] }
12+
rand = "0.8.5"

fuzzy_test_files/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.py

fuzzy_test_files_unparsed/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.py

pdm.lock

+37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "rustpython-unparser"
3+
version = "0.1.0"
4+
description = "Default template for PDM package"
5+
authors = [
6+
{name = "Jan Vollmer", email = "[email protected]"},
7+
]
8+
dependencies = ["pysource-codegen>=0.6.0"]
9+
requires-python = ">=3.10"
10+
readme = "README.md"
11+
license = {text = "MIT"}
12+
13+
14+
[tool.pdm]
15+
distribution = false

src/lib.rs

+36-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod tests {
1515
use std::fs;
1616
use std::io;
1717
use std::path::Path;
18+
use std::process::Command;
1819

1920
struct RangesEraser {}
2021

@@ -38,14 +39,18 @@ mod tests {
3839
}
3940
}
4041

41-
#[test]
42-
fn test_predefined_files() -> io::Result<()> {
43-
for entry in fs::read_dir("./test_files")? {
42+
fn run_tests_on_folders(source_folder: &str, results_folder: &str) -> io::Result<()> {
43+
for entry in fs::read_dir(source_folder)? {
4444
let entry = entry?;
4545

4646
let entry_path = entry.path();
4747

48-
if entry_path.is_file() {
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+
{
4954
let file_content = fs::read_to_string(&entry_path)?;
5055
let entry_path_str = entry_path.to_str().unwrap();
5156
let mut unparser = Unparser::new();
@@ -56,7 +61,7 @@ mod tests {
5661
let new_source = unparser.source;
5762
let old_file_name = entry_path.file_name().unwrap().to_str().unwrap();
5863
let new_file_name = old_file_name.replace(".py", "_unparsed.py");
59-
let new_entry_path_str = format!("./test_files_unparsed/{}", new_file_name);
64+
let new_entry_path_str = format!("{}/{}", results_folder, new_file_name);
6065
let new_entry_path = Path::new(&new_entry_path_str);
6166
fs::write(&new_entry_path, &new_source)?;
6267
let new_stmts =
@@ -80,4 +85,30 @@ mod tests {
8085
}
8186
Ok(())
8287
}
88+
89+
#[test]
90+
fn test_predefined_files() -> io::Result<()> {
91+
run_tests_on_folders("./test_files", "./test_files_unparsed")
92+
}
93+
#[test]
94+
#[ignore = "Fuzzy tests are unstable and should only be used to explore new test cases"]
95+
fn test_fuzzy_files() -> io::Result<()> {
96+
let seed = rand::random::<usize>();
97+
98+
for i in 0..10 {
99+
let file_name = format!("./fuzzy_test_files/fuzzy_test{}.py", i);
100+
101+
let file_content = Command::new(".venv/bin/python")
102+
.arg("-m")
103+
.arg("pysource_codegen")
104+
.arg("--seed")
105+
.arg(&seed.to_string())
106+
.output()
107+
.expect("failed to execute process");
108+
fs::write(&file_name, &file_content.stdout)?;
109+
}
110+
111+
run_tests_on_folders("./fuzzy_test_files", "./fuzzy_test_files_unparsed")?;
112+
Ok(())
113+
}
83114
}

src/unparser.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -790,14 +790,17 @@ impl Unparser {
790790
for generator in &node.generators {
791791
self.unparse_comprehension(generator);
792792
}
793+
self.write_str("}");
793794
}
794795

795796
fn unparse_expr_generator_exp(&mut self, node: &ExprGeneratorExp<TextRange>) {
797+
self.write_str("(");
796798
self.unparse_expr(&node.elt);
797799

798800
for generator in &node.generators {
799801
self.unparse_comprehension(generator);
800802
}
803+
self.write_str(")");
801804
}
802805

803806
fn unparse_expr_await(&mut self, node: &ExprAwait<TextRange>) {
@@ -1023,9 +1026,9 @@ impl Unparser {
10231026

10241027
fn unparse_comprehension(&mut self, node: &Comprehension<TextRange>) {
10251028
if node.is_async {
1026-
self.write_str("async for");
1029+
self.write_str(" async for ");
10271030
} else {
1028-
self.write_str("for");
1031+
self.write_str(" for ");
10291032
}
10301033
self.unparse_expr(&node.target);
10311034
self.write_str(" in ");
@@ -1045,12 +1048,21 @@ impl Unparser {
10451048
&mut self,
10461049
node: &ExceptHandlerExceptHandler<TextRange>,
10471050
) {
1051+
self.fill("except");
1052+
if self.in_try_star {
1053+
self.write_str("*")
1054+
}
1055+
10481056
if let Some(type_) = &node.type_ {
1057+
self.write_str(" ");
10491058
self.unparse_expr(type_);
10501059
}
1051-
for stmt in &node.body {
1052-
self.unparse_stmt(stmt);
1053-
}
1060+
self.write_str(":");
1061+
self.block(|block_self| {
1062+
for stmt in &node.body {
1063+
block_self.unparse_stmt(stmt);
1064+
}
1065+
});
10541066
}
10551067

10561068
fn unparse_arguments(&mut self, node: &Arguments<TextRange>) {

test_files/simple_comprehensions.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import random
2+
import string
3+
4+
generator = (value for value in random.choices(string.ascii_letters))
5+
set_ = {def_ for def_ in random.choices(string.ascii_letters)}
6+
dict_ = {k: v for k, v in enumerate(random.choices(string.ascii_letters))}
7+
list_ = [value for value in random.choices(string.ascii_letters)]

test_files/simple_try_except.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# example comes from fuzzing
2+
3+
try:
4+
name_0 # type: ignore # noqa: F821
5+
name_4 # type: ignore # noqa: F821
6+
name_4 # type: ignore # noqa: F821
7+
name_1 # type: ignore # noqa: F821
8+
except name_1: # type: ignore # noqa: F821
9+
pass
10+
except name_4: # type: ignore # noqa: F821
11+
pass
12+
pass
13+
except: # noqa: E722
14+
pass
15+
pass
16+
else:
17+
pass
18+
pass
19+
finally:
20+
pass
21+
pass
22+
pass
23+
pass
24+
pass

0 commit comments

Comments
 (0)