Skip to content

Commit aa4ace8

Browse files
authored
test: adds tests to confirm proper execution of list_code_objects() (#2320)
1 parent 7effe2c commit aa4ace8

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2025 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import os
18+
import pytest
19+
from unittest import mock
20+
21+
from scripts.microgenerator import generate
22+
23+
# Mock data to be returned by parse_file
24+
MOCK_ANALYZED_CLASSES = [
25+
{
26+
"class_name": "MyClass",
27+
"methods": [
28+
{
29+
"method_name": "my_method",
30+
"args": [{"name": "self"}, {"name": "a", "type": "int"}],
31+
"return_type": "None",
32+
},
33+
{
34+
"method_name": "another_method",
35+
"args": [{"name": "self"}],
36+
"return_type": "str",
37+
},
38+
],
39+
"attributes": [{"name": "my_attr", "type": "str"}],
40+
},
41+
{
42+
"class_name": "AnotherClass",
43+
"methods": [],
44+
"attributes": [],
45+
},
46+
]
47+
48+
MOCK_ANALYZED_CLASSES_B = [
49+
{
50+
"class_name": "ClassB",
51+
"methods": [],
52+
"attributes": [],
53+
}
54+
]
55+
56+
57+
@pytest.fixture
58+
def temp_py_file(tmp_path):
59+
d = tmp_path / "sub"
60+
d.mkdir()
61+
p = d / "test_file.py"
62+
p.write_text("class TestClass:\n pass")
63+
return str(p)
64+
65+
66+
@pytest.fixture
67+
def temp_py_dir(tmp_path):
68+
d = tmp_path / "dir"
69+
d.mkdir()
70+
p1 = d / "file1.py"
71+
p1.write_text("class File1Class:\n pass")
72+
p2 = d / "file2.py"
73+
p2.write_text("class File2Class:\n pass")
74+
return str(d)
75+
76+
77+
@pytest.fixture
78+
def temp_empty_py_file(tmp_path):
79+
p = tmp_path / "empty.py"
80+
p.write_text("")
81+
return str(p)
82+
83+
84+
@pytest.fixture
85+
def temp_no_classes_py_file(tmp_path):
86+
p = tmp_path / "no_classes.py"
87+
p.write_text("def some_function():\n pass")
88+
return str(p)
89+
90+
91+
@mock.patch("scripts.microgenerator.generate.parse_file")
92+
def test_list_code_objects_classes_only(mock_parse_file, temp_py_file):
93+
mock_parse_file.return_value = (MOCK_ANALYZED_CLASSES, set(), set())
94+
# show_methods, show_attributes, show_arguments default to False so only classes show up
95+
result = generate.list_code_objects(temp_py_file)
96+
assert result == ["AnotherClass", "MyClass"]
97+
mock_parse_file.assert_called_once_with(temp_py_file)
98+
99+
100+
@mock.patch("scripts.microgenerator.generate.parse_file")
101+
def test_list_code_objects_show_methods(mock_parse_file, temp_py_file):
102+
mock_parse_file.return_value = (MOCK_ANALYZED_CLASSES, set(), set())
103+
# show_attributes, show_arguments default to False and do not show up
104+
result = generate.list_code_objects(temp_py_file, show_methods=True)
105+
assert "MyClass" in result
106+
assert "AnotherClass" in result
107+
assert result["MyClass"]["methods"] == ["another_method", "my_method"]
108+
assert result["AnotherClass"]["methods"] == []
109+
110+
111+
@mock.patch("scripts.microgenerator.generate.parse_file")
112+
def test_list_code_objects_show_attributes(mock_parse_file, temp_py_file):
113+
mock_parse_file.return_value = (MOCK_ANALYZED_CLASSES, set(), set())
114+
# show_methods, show_arguments default to False and do not show up
115+
result = generate.list_code_objects(temp_py_file, show_attributes=True)
116+
assert "MyClass" in result
117+
assert result["MyClass"]["attributes"] == [{"name": "my_attr", "type": "str"}]
118+
assert "AnotherClass" in result
119+
assert result["AnotherClass"]["attributes"] == []
120+
121+
122+
@mock.patch("scripts.microgenerator.generate.parse_file")
123+
def test_list_code_objects_show_arguments(mock_parse_file, temp_py_file):
124+
mock_parse_file.return_value = (MOCK_ANALYZED_CLASSES, set(), set())
125+
# show_arguments=True implies show_methods=True; show_attributes defaults to False.
126+
result = generate.list_code_objects(temp_py_file, show_arguments=True)
127+
assert "MyClass" in result
128+
assert "methods" in result["MyClass"]
129+
assert result["MyClass"]["methods"]["my_method"] == [
130+
{"name": "self"},
131+
{"name": "a", "type": "int"},
132+
]
133+
assert result["MyClass"]["methods"]["another_method"] == [{"name": "self"}]
134+
135+
136+
@mock.patch("scripts.microgenerator.generate.parse_file")
137+
def test_list_code_objects_all_flags(mock_parse_file, temp_py_file):
138+
mock_parse_file.return_value = (MOCK_ANALYZED_CLASSES, set(), set())
139+
# all show_* parameters are set to True, so if present in the code all elements will show up
140+
result = generate.list_code_objects(
141+
temp_py_file, show_methods=True, show_attributes=True, show_arguments=True
142+
)
143+
assert "MyClass" in result
144+
assert "methods" in result["MyClass"]
145+
assert "attributes" in result["MyClass"]
146+
assert result["MyClass"]["methods"]["my_method"] == [
147+
{"name": "self"},
148+
{"name": "a", "type": "int"},
149+
]
150+
assert result["MyClass"]["attributes"] == [{"name": "my_attr", "type": "str"}]
151+
152+
153+
@mock.patch("scripts.microgenerator.utils.walk_codebase")
154+
@mock.patch("scripts.microgenerator.generate.parse_file")
155+
def test_list_code_objects_directory(mock_parse_file, mock_walk_codebase, temp_py_dir):
156+
file1 = os.path.join(temp_py_dir, "file1.py")
157+
file2 = os.path.join(temp_py_dir, "file2.py")
158+
mock_walk_codebase.return_value = [file1, file2]
159+
160+
def side_effect(path):
161+
if path == file1:
162+
return (MOCK_ANALYZED_CLASSES, set(), set())
163+
if path == file2:
164+
return (MOCK_ANALYZED_CLASSES_B, set(), set())
165+
return ([], set(), set())
166+
167+
mock_parse_file.side_effect = side_effect
168+
# show_methods, show_attributes, show_arguments default to False so only classes show up
169+
result = generate.list_code_objects(temp_py_dir)
170+
assert sorted(result) == [
171+
"AnotherClass (in file1.py)",
172+
"ClassB (in file2.py)",
173+
"MyClass (in file1.py)",
174+
]
175+
176+
177+
@mock.patch("scripts.microgenerator.generate.parse_file")
178+
def test_list_code_objects_empty_file(mock_parse_file, temp_empty_py_file):
179+
mock_parse_file.return_value = ([], set(), set())
180+
# empty file, nothing should be returned
181+
result = generate.list_code_objects(temp_empty_py_file)
182+
assert result == []
183+
# empty file, nothing should be returned
184+
result_dict = generate.list_code_objects(temp_empty_py_file, show_methods=True)
185+
assert result_dict == {}
186+
187+
188+
@mock.patch("scripts.microgenerator.generate.parse_file")
189+
def test_list_code_objects_no_classes(mock_parse_file, temp_no_classes_py_file):
190+
mock_parse_file.return_value = ([], set(), set())
191+
# file has no classes, nothing should be returned
192+
result = generate.list_code_objects(temp_no_classes_py_file)
193+
assert result == []
194+
# file has no classes, nothing should be returned
195+
result_dict = generate.list_code_objects(
196+
temp_no_classes_py_file, show_attributes=True
197+
)
198+
assert result_dict == {}

0 commit comments

Comments
 (0)