testgenie-py 0.3.7__py3-none-any.whl → 0.3.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- testgen/analyzer/ast_analyzer.py +2 -11
- testgen/analyzer/fuzz_analyzer.py +1 -6
- testgen/analyzer/random_feedback_analyzer.py +20 -293
- testgen/analyzer/reinforcement_analyzer.py +59 -57
- testgen/analyzer/test_case_analyzer_context.py +0 -6
- testgen/controller/cli_controller.py +35 -29
- testgen/controller/docker_controller.py +1 -0
- testgen/db/dao.py +68 -0
- testgen/db/dao_impl.py +226 -0
- testgen/{sqlite → db}/db.py +15 -6
- testgen/generator/pytest_generator.py +2 -10
- testgen/generator/unit_test_generator.py +2 -11
- testgen/main.py +1 -3
- testgen/models/coverage_data.py +56 -0
- testgen/models/db_test_case.py +65 -0
- testgen/models/function.py +56 -0
- testgen/models/function_metadata.py +11 -1
- testgen/models/generator_context.py +30 -3
- testgen/models/source_file.py +29 -0
- testgen/models/test_result.py +38 -0
- testgen/models/test_suite.py +20 -0
- testgen/reinforcement/agent.py +1 -27
- testgen/reinforcement/environment.py +11 -93
- testgen/reinforcement/statement_coverage_state.py +5 -4
- testgen/service/analysis_service.py +31 -22
- testgen/service/cfg_service.py +3 -1
- testgen/service/coverage_service.py +115 -0
- testgen/service/db_service.py +140 -0
- testgen/service/generator_service.py +77 -20
- testgen/service/logging_service.py +2 -2
- testgen/service/service.py +62 -231
- testgen/service/test_executor_service.py +145 -0
- testgen/util/coverage_utils.py +38 -116
- testgen/util/coverage_visualizer.py +10 -9
- testgen/util/file_utils.py +10 -111
- testgen/util/randomizer.py +0 -26
- testgen/util/utils.py +197 -38
- {testgenie_py-0.3.7.dist-info → testgenie_py-0.3.9.dist-info}/METADATA +1 -1
- testgenie_py-0.3.9.dist-info/RECORD +72 -0
- testgen/inspector/inspector.py +0 -59
- testgen/presentation/__init__.py +0 -0
- testgen/presentation/cli_view.py +0 -12
- testgen/sqlite/__init__.py +0 -0
- testgen/sqlite/db_service.py +0 -239
- testgen/testgen.db +0 -0
- testgenie_py-0.3.7.dist-info/RECORD +0 -67
- /testgen/{inspector → db}/__init__.py +0 -0
- {testgenie_py-0.3.7.dist-info → testgenie_py-0.3.9.dist-info}/WHEEL +0 -0
- {testgenie_py-0.3.7.dist-info → testgenie_py-0.3.9.dist-info}/entry_points.txt +0 -0
testgen/util/utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
|
2
2
|
import ast
|
3
|
+
import os
|
3
4
|
import string
|
4
5
|
import sys
|
5
6
|
import random
|
@@ -7,12 +8,11 @@ from typing import List
|
|
7
8
|
|
8
9
|
from atheris import FuzzedDataProvider
|
9
10
|
|
11
|
+
import testgen.util.file_utils
|
12
|
+
from testgen.models.function import Function
|
13
|
+
from testgen.models.test_case import TestCase
|
10
14
|
from testgen.util.file_utils import load_and_parse_file_for_tree
|
11
15
|
|
12
|
-
|
13
|
-
def get_func(module, func_name:str):
|
14
|
-
return getattr(module, func_name)
|
15
|
-
|
16
16
|
def extract_parameter_types(func_node):
|
17
17
|
"""Extract parameter types from a function node."""
|
18
18
|
param_types = {}
|
@@ -51,39 +51,6 @@ def generate_random_inputs(param_types):
|
|
51
51
|
inputs[param] = None
|
52
52
|
return inputs
|
53
53
|
|
54
|
-
def generate_extreme_inputs(param_types):
|
55
|
-
inputs = {}
|
56
|
-
for param, param_type in param_types.items():
|
57
|
-
if param_type == "int":
|
58
|
-
# int is unbounded in Python, but sys.maxsize is the max value representable by a signed word
|
59
|
-
inputs[param] = sys.maxsize
|
60
|
-
if param_type == "bool":
|
61
|
-
random_choice = random.choice([1, 0])
|
62
|
-
inputs[param] = random_choice
|
63
|
-
if param_type == "float":
|
64
|
-
random_choice = random.choice([sys.float_info.min, sys.float_info.max])
|
65
|
-
inputs[param] = random_choice
|
66
|
-
if param_type == "str":
|
67
|
-
inputs[param] = ''.join(random.choice([string.ascii_letters, string.digits, string.punctuation, string.whitespace]) for _ in range(100))
|
68
|
-
|
69
|
-
def generate_inputs_from_fuzz_data(fdp: FuzzedDataProvider, param_types):
|
70
|
-
"""Generate fuzzed inputs based on parameter types."""
|
71
|
-
inputs = []
|
72
|
-
for param_type in param_types.values():
|
73
|
-
if param_type == "int":
|
74
|
-
inputs.append(fdp.ConsumeInt(4))
|
75
|
-
elif param_type == "bool":
|
76
|
-
inputs.append(fdp.ConsumeBool())
|
77
|
-
elif param_type == "float":
|
78
|
-
inputs.append(fdp.ConsumeFloat())
|
79
|
-
elif param_type == "str":
|
80
|
-
inputs.append(fdp.ConsumeString(10))
|
81
|
-
elif param_type == "bytes":
|
82
|
-
inputs.append(fdp.ConsumeBytes(10))
|
83
|
-
else:
|
84
|
-
inputs.append(None)
|
85
|
-
return tuple(inputs)
|
86
|
-
|
87
54
|
def get_functions(tree) -> List[ast.FunctionDef]:
|
88
55
|
functions = []
|
89
56
|
for node in tree.body:
|
@@ -95,6 +62,41 @@ def get_functions(tree) -> List[ast.FunctionDef]:
|
|
95
62
|
functions.append(class_node)
|
96
63
|
return functions
|
97
64
|
|
65
|
+
def get_list_of_functions(filepath: str) -> List[Function]:
|
66
|
+
tree = load_and_parse_file_for_tree(filepath)
|
67
|
+
functions = []
|
68
|
+
for node in tree.body:
|
69
|
+
if isinstance(node, ast.FunctionDef):
|
70
|
+
params = extract_parameter_types(node)
|
71
|
+
start_line = node.lineno
|
72
|
+
end_line = max(
|
73
|
+
[line.lineno for line in ast.walk(node) if hasattr(line, 'lineno') and line.lineno],
|
74
|
+
default=start_line
|
75
|
+
)
|
76
|
+
num_lines = end_line - start_line + 1
|
77
|
+
name = node.name
|
78
|
+
source_file_id = -1 # Placeholder
|
79
|
+
functions.append(Function(name, str(params), start_line, end_line, num_lines, source_file_id))
|
80
|
+
if isinstance(node, ast.ClassDef):
|
81
|
+
for method in node.body:
|
82
|
+
if isinstance(method, ast.FunctionDef):
|
83
|
+
params = extract_parameter_types(method)
|
84
|
+
start_line = method.lineno
|
85
|
+
end_line = max(
|
86
|
+
[line.lineno for line in ast.walk(method) if hasattr(line, 'lineno') and line.lineno],
|
87
|
+
default=start_line
|
88
|
+
)
|
89
|
+
num_lines = end_line - start_line + 1
|
90
|
+
|
91
|
+
# Method name (prefixed with class name for clarity)
|
92
|
+
name = f"{node.name}.{method.name}"
|
93
|
+
|
94
|
+
# Source file ID will be set when adding to database
|
95
|
+
source_file_id = -1 # Placeholder
|
96
|
+
|
97
|
+
functions.append(Function(name, str(params), start_line, end_line, num_lines, source_file_id))
|
98
|
+
return functions
|
99
|
+
|
98
100
|
def get_function_boundaries(file_name: str, func_name: str) -> tuple:
|
99
101
|
tree = load_and_parse_file_for_tree(file_name)
|
100
102
|
for i, node in enumerate(tree.body):
|
@@ -139,5 +141,162 @@ def get_function_boundaries(file_name: str, func_name: str) -> tuple:
|
|
139
141
|
|
140
142
|
raise ValueError(f"Function {func_name} not found in {file_name}")
|
141
143
|
|
142
|
-
|
144
|
+
def parse_test_case_from_result_name(name: str, format: int) -> TestCase:
|
145
|
+
test_case = TestCase(None, (), None)
|
146
|
+
if format == 3:
|
147
|
+
test_case = parse_test_case_from_result_name_doctest(name)
|
148
|
+
elif format == 2:
|
149
|
+
test_case = parse_test_case_from_result_name_pytest(name, format)
|
150
|
+
elif format == 1:
|
151
|
+
test_case = parse_test_case_from_result_name_unittest(name, format)
|
152
|
+
else:
|
153
|
+
raise ValueError(f"Unsupported format: {format}")
|
154
|
+
return test_case
|
155
|
+
|
156
|
+
def parse_test_case_from_result_name_pytest(name: str, format: int) -> TestCase:
|
157
|
+
parts_name = name.split("::")
|
158
|
+
test_filepath = parts_name[0]
|
159
|
+
print(f"Parse test case from result name pytest: {test_filepath}")
|
160
|
+
test_filepath = os.path.abspath(os.path.join(os.getcwd(), test_filepath))
|
161
|
+
test_function_name = parts_name[1]
|
162
|
+
|
163
|
+
if test_function_name.startswith("test_"):
|
164
|
+
function_name_with_int = test_function_name[5:]
|
165
|
+
else:
|
166
|
+
function_name_with_int = test_function_name
|
167
|
+
|
168
|
+
parts = function_name_with_int.rsplit('_', 1)
|
169
|
+
if len(parts) > 1 and parts[1].isdigit():
|
170
|
+
function_name = parts[0]
|
171
|
+
else:
|
172
|
+
function_name = function_name_with_int
|
173
|
+
|
174
|
+
args, expected = extract_test_case_data(test_filepath, test_function_name, format)
|
175
|
+
|
176
|
+
return TestCase(function_name, args, expected)
|
177
|
+
|
178
|
+
def parse_test_case_from_result_name_unittest(name: str, format: int) -> TestCase:
|
179
|
+
parts_name = name.split("::")
|
180
|
+
test_filepath = parts_name[0]
|
181
|
+
test_function_name_with_mod = parts_name[1]
|
182
|
+
test_function_name = test_function_name_with_mod.split('.')[1]
|
183
|
+
|
184
|
+
args, expected = extract_test_case_data(test_filepath, test_function_name, format)
|
185
|
+
|
186
|
+
parts = test_function_name.rsplit('_', 1)
|
187
|
+
if len(parts) > 1 and parts[1].isdigit():
|
188
|
+
function_name = parts[0]
|
189
|
+
else:
|
190
|
+
function_name = test_function_name
|
191
|
+
|
192
|
+
return TestCase(function_name, args, expected)
|
193
|
+
|
194
|
+
# TODO: Find a way to associate doctest results to the actual test case
|
195
|
+
def parse_test_case_from_result_name_doctest(name: str) -> TestCase:
|
196
|
+
function_name = name.split("::")[1]
|
197
|
+
return TestCase(function_name, (), None)
|
198
|
+
|
199
|
+
def extract_test_case_data(test_file_path: str, function_name: str, test_format: int):
|
200
|
+
if not os.path.exists(test_file_path):
|
201
|
+
print(f"Error: Test file not found: {test_file_path}")
|
202
|
+
return {}, None
|
203
|
+
|
204
|
+
try:
|
205
|
+
with open(test_file_path, 'r') as f:
|
206
|
+
file_content = f.read()
|
207
|
+
|
208
|
+
# For doctests, just return empty data since we can't easily extract it
|
209
|
+
if test_format == 3: # Doctest format
|
210
|
+
return {}, None
|
211
|
+
|
212
|
+
# Parse the file using ast
|
213
|
+
tree = ast.parse(file_content)
|
214
|
+
|
215
|
+
# Look for test function definition based on format
|
216
|
+
if test_format == 1: # Unittest
|
217
|
+
return _extract_unittest_data(tree, function_name)
|
218
|
+
elif test_format == 2: # Pytest
|
219
|
+
return _extract_pytest_data(tree, function_name)
|
220
|
+
else:
|
221
|
+
return None
|
222
|
+
|
223
|
+
except Exception as e:
|
224
|
+
print(f"Error extracting test case data: {e}")
|
225
|
+
return None
|
226
|
+
|
227
|
+
def _extract_unittest_data(tree: ast.AST, function_name: str):
|
228
|
+
for node in tree.body:
|
229
|
+
if isinstance(node, ast.ClassDef):
|
230
|
+
# Look for test method within the class
|
231
|
+
for class_node in node.body:
|
232
|
+
if isinstance(class_node, ast.FunctionDef) and class_node.name == f"test_{function_name}":
|
233
|
+
# Find 'args =' and 'expected =' assignments in the function body
|
234
|
+
args_value = None
|
235
|
+
expected_value = None
|
236
|
+
|
237
|
+
for stmt in class_node.body:
|
238
|
+
if isinstance(stmt, ast.Assign):
|
239
|
+
if isinstance(stmt.targets[0], ast.Name):
|
240
|
+
if stmt.targets[0].id == "args":
|
241
|
+
args_value = _extract_value(stmt.value)
|
242
|
+
elif stmt.targets[0].id == "expected":
|
243
|
+
expected_value = _extract_value(stmt.value)
|
244
|
+
|
245
|
+
if args_value is not None:
|
246
|
+
# Convert args to dict for consistency
|
247
|
+
if isinstance(args_value, tuple):
|
248
|
+
return args_value, expected_value
|
249
|
+
else:
|
250
|
+
return (args_value,), expected_value
|
251
|
+
|
252
|
+
return None
|
253
|
+
|
254
|
+
def _extract_pytest_data(tree: ast.AST, function_name: str):
|
255
|
+
"""Extract args and expected output from pytest-style test file"""
|
256
|
+
for node in tree.body:
|
257
|
+
if isinstance(node, ast.FunctionDef) and node.name == f"test_{function_name}":
|
258
|
+
# Find 'args =' and 'expected =' assignments in the function body
|
259
|
+
args_value = None
|
260
|
+
expected_value = None
|
261
|
+
|
262
|
+
for stmt in node.body:
|
263
|
+
if isinstance(stmt, ast.Assign):
|
264
|
+
if isinstance(stmt.targets[0], ast.Name):
|
265
|
+
if stmt.targets[0].id == "args":
|
266
|
+
args_value = _extract_value(stmt.value)
|
267
|
+
elif stmt.targets[0].id == "expected":
|
268
|
+
expected_value = _extract_value(stmt.value)
|
269
|
+
|
270
|
+
if args_value is not None:
|
271
|
+
# Convert args to dict for consistency
|
272
|
+
if isinstance(args_value, tuple):
|
273
|
+
return args_value, expected_value
|
274
|
+
else:
|
275
|
+
return (args_value,), expected_value
|
276
|
+
|
277
|
+
return None
|
278
|
+
|
279
|
+
def _extract_value(node: ast.AST):
|
280
|
+
"""Convert an AST node to its Python value"""
|
281
|
+
if isinstance(node, ast.Tuple):
|
282
|
+
return tuple(_extract_value(elt) for elt in node.elts)
|
283
|
+
elif isinstance(node, ast.List):
|
284
|
+
return [_extract_value(elt) for elt in node.elts]
|
285
|
+
elif isinstance(node, ast.Dict):
|
286
|
+
return {_extract_value(key): _extract_value(value) for key, value in zip(node.keys, node.values)}
|
287
|
+
elif isinstance(node, ast.Constant):
|
288
|
+
return node.value
|
289
|
+
|
290
|
+
|
291
|
+
# If we can't determine the value, try using ast.unparse (Python 3.9+)
|
292
|
+
try:
|
293
|
+
return eval(ast.unparse(node))
|
294
|
+
except:
|
295
|
+
try:
|
296
|
+
# Fallback for older Python versions
|
297
|
+
code = compile(ast.Expression(node), '<string>', 'eval')
|
298
|
+
return eval(code)
|
299
|
+
except:
|
300
|
+
return None
|
301
|
+
|
143
302
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
testgen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
testgen/analyzer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
testgen/analyzer/ast_analyzer.py,sha256=yHpGJLVlyTngcBED50I6j_sRBCIdoT9l7eWIFCsfbwQ,6307
|
4
|
+
testgen/analyzer/contracts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
testgen/analyzer/contracts/contract.py,sha256=6rNYJOy_2GrOhGtaXTDIOX6pTEOqo856FxG0yTc8amI,369
|
6
|
+
testgen/analyzer/contracts/no_exception_contract.py,sha256=rTWTuu5XKmvzBPD6yNAqiNehk9lbWn_Z8zFj-_djZ_w,512
|
7
|
+
testgen/analyzer/contracts/nonnull_contract.py,sha256=uurnrVptImYTVpSnu8ckdlU6c94AbiOWFoH1YnAQU0w,508
|
8
|
+
testgen/analyzer/fuzz_analyzer.py,sha256=dzdMoJmosSgOfXCY-EhSLIM6vsdGeKiDqXj0iLS9qqk,3988
|
9
|
+
testgen/analyzer/random_feedback_analyzer.py,sha256=1Hj2Zxhr98L35estFGFj8KHTEeo1dbccYiX_Tvz1nmw,11755
|
10
|
+
testgen/analyzer/reinforcement_analyzer.py,sha256=rz47udd9DplWmnDeYtwf8_udcXqyLIBMwiz51KdMtQ4,2900
|
11
|
+
testgen/analyzer/test_case_analyzer.py,sha256=0qLcRKOrQ4Uyh1ZMecVTkY9bWjNnx7OridigNyjfUk4,1620
|
12
|
+
testgen/analyzer/test_case_analyzer_context.py,sha256=akOsq3xnG6himbwb4vpxcYthySj9rhRwXjoKBdFuW5c,1911
|
13
|
+
testgen/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
testgen/controller/cli_controller.py,sha256=HXB8KvOkt8tWDFxP3NP5oVRlhRCCK6BpAn4BXyuZF8Q,8835
|
15
|
+
testgen/controller/docker_controller.py,sha256=1GqRstLLl93JRDfn0mTmWABYl2fYVxA6QN_1aqhe0w4,9236
|
16
|
+
testgen/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
+
testgen/db/dao.py,sha256=WXCXMrxHyqkB7oURPr0k_C5yDj8ifStZcn8nvpO5M5s,1957
|
18
|
+
testgen/db/dao_impl.py,sha256=nE3JmhL_stOQ0XiIjK0_E31z1bHpwmNBTOW4HQvQIUg,8678
|
19
|
+
testgen/db/db.py,sha256=ssQWNihmsyWMsmDWHNvWuYR63mtmUSxljMRJ3PwkUQU,2887
|
20
|
+
testgen/docker/Dockerfile,sha256=6mutzP0ZkVueuvMLCOy2bsyGxWjLHU-cq7RFkuC8r5Y,781
|
21
|
+
testgen/generator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
+
testgen/generator/code_generator.py,sha256=-Nh5Jt4my29YWA_LKWn98I4jvDA-fNi6AGuw-vJhRV0,3171
|
23
|
+
testgen/generator/doctest_generator.py,sha256=B9I-nw7G7ubcx51XLOl1kiOEL0YW0aDuCAKebqfkRRI,8751
|
24
|
+
testgen/generator/generator.py,sha256=9M9zg8DO0uU5W5s9hsz2oPCL7bz7sAD087M5TYottSs,2007
|
25
|
+
testgen/generator/pytest_generator.py,sha256=UH1olGSePOtJ9zO1r8FsSV24tfO5c1cFoD5m7v5yTI0,2888
|
26
|
+
testgen/generator/test_generator.py,sha256=D2Y3DaWH4fdIc5_9Xrznrkm0urFfhpYxuhL81M7RRaw,710
|
27
|
+
testgen/generator/unit_test_generator.py,sha256=RwWJ2Pg5ITQx9ZInOtLbwGbjeXXDA1cjLsa9EG__RUk,3114
|
28
|
+
testgen/main.py,sha256=sDaLZOKpw2PuExHYr10VDTrglbbWvBfKS6QCqUrHyrE,308
|
29
|
+
testgen/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
testgen/models/analysis_context.py,sha256=-mhw_hn26MgF6aJnmj6Hub5jfCi8gmvcf4QNM4l7G2s,1442
|
31
|
+
testgen/models/coverage_data.py,sha256=yW9ednji7iU9Dc_1xU5KIU6MbIzSloWVqlysy_1sLZ4,1661
|
32
|
+
testgen/models/db_test_case.py,sha256=yQdU-4mfeVV3eSe1dN9xtF83SJcm77dJH9QbbUvZbDY,1777
|
33
|
+
testgen/models/function.py,sha256=M-vAkvWnqxJjazT6n0xbirHNjzhrcg4pZewRvzX0PKc,1390
|
34
|
+
testgen/models/function_metadata.py,sha256=32udGhHN1-gDy--Np1Azli-OXNLbvspyytlrVBfaQFQ,1795
|
35
|
+
testgen/models/generator_context.py,sha256=TtPh5Td8jrEScvpwIuN2qfS31ZP8CAHL6a7vren5x4s,2462
|
36
|
+
testgen/models/source_file.py,sha256=-o6MKvvdrlDYZX7qSuAa6F02i25F3i4ckGRQa5M5x5o,738
|
37
|
+
testgen/models/test_case.py,sha256=jwodn-6fKHCh4SO2qycbrFyggCb9UlwTZ8k3RdTObDQ,216
|
38
|
+
testgen/models/test_result.py,sha256=YFH84ySbhxNotnlnZCWMSLrXrrw4zaABJjrLLJzlVxE,955
|
39
|
+
testgen/models/test_suite.py,sha256=hcKX4WSPZ7f2k_Qw24hPrkoO-_JQ49JX7E0BxatGOfg,475
|
40
|
+
testgen/reinforcement/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
+
testgen/reinforcement/abstract_state.py,sha256=M9Kp8fyJzsTzpyL3ca4HjjYpGXy9DRMw-IG7OgFVisY,154
|
42
|
+
testgen/reinforcement/agent.py,sha256=lM9ZgJKQZ2_SfBG0-U0RWYwU_nyBL5Upc8yC2JbU_o0,5270
|
43
|
+
testgen/reinforcement/environment.py,sha256=8rFSSFjnhkSvS_HSd-VC1J6iBOmoD2imsGFI-MN1fq8,6415
|
44
|
+
testgen/reinforcement/statement_coverage_state.py,sha256=N8blj5MBO5V0oxJZLnpoWnTqK3KAu7rTm1CsltsR93M,1743
|
45
|
+
testgen/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
+
testgen/service/analysis_service.py,sha256=Vl9DgxX_JcIOsIMMjEajyT_uys3DPvmlgzdwFQKvRRk,11508
|
47
|
+
testgen/service/cfg_service.py,sha256=qjBaUFXPVMAdysx5OcWjIH0d4qOeaSijwPHYVPNglPY,2351
|
48
|
+
testgen/service/coverage_service.py,sha256=zpbEJMU23WmfjdPfekkYdDeUKa0WZvJILfk6OYAbG_A,4573
|
49
|
+
testgen/service/db_service.py,sha256=BiDUZni647CrePFOS4Ia-wf62j0gYKq3lsEOZphn0Pc,7081
|
50
|
+
testgen/service/generator_service.py,sha256=CyzYIM2yp-7h05uTCuhAlPkxL6ZP3Q4N9NdZFn-kSDI,10764
|
51
|
+
testgen/service/logging_service.py,sha256=X0HhvSFPXj08634cOfFgq_o685JTlzoNPriUZLpBpac,3195
|
52
|
+
testgen/service/service.py,sha256=v5bfQ-mxlyBv1iQQYD06JKgU0xAWgnAVrpKKEyTizBM,14984
|
53
|
+
testgen/service/test_executor_service.py,sha256=-37-DSQUmD4cSPC098raV8eYhHlpU11yoqNxBwSS34U,5685
|
54
|
+
testgen/tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
+
testgen/tree/node.py,sha256=ONJtbACShN4yNj1X-UslFRgLyBP6mrbg7qZr3c6dWyA,165
|
56
|
+
testgen/tree/tree_utils.py,sha256=gT7jucky6_GWVOlDI6jpv6RMeWCkntGOHIYLvHxD85k,2122
|
57
|
+
testgen/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
|
+
testgen/util/coverage_utils.py,sha256=2jY7I3QFbIWNvpkZDl5jVCd9NJh2_fxiptbsg__MPf4,5666
|
59
|
+
testgen/util/coverage_visualizer.py,sha256=arGU7gl5Gm0uByZrc4sGSrJ_2DdY71UG2mOU8-C_Ue0,5974
|
60
|
+
testgen/util/file_utils.py,sha256=bfGZ8zZ6QHmnC9VkuYkDFgose586v8GokGGkidApypA,3004
|
61
|
+
testgen/util/randomizer.py,sha256=8UqqVDk13N7SnC-8HrBi6wjl7RhAgfh2TMVN5u-ZQxk,4661
|
62
|
+
testgen/util/utils.py,sha256=KqJsd54iD4LS0KbsBM3hS0XAdQhdCgkJmGs-bypKb6c,12212
|
63
|
+
testgen/util/z3_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
|
+
testgen/util/z3_utils/ast_to_z3.py,sha256=V87cvlH2OlO7_owuVTqygymMmK_FyesBovi6qe-BUcg,3180
|
65
|
+
testgen/util/z3_utils/branch_condition.py,sha256=N9FNR-iJmxIC62NpDQNVZ1OP14rXXqYzdA_NODPDUm4,2453
|
66
|
+
testgen/util/z3_utils/constraint_extractor.py,sha256=RXJLpmk6dAvHZ27839VXKXNtdy9St1F-17-pSEFu4bM,1285
|
67
|
+
testgen/util/z3_utils/variable_finder.py,sha256=dUh3F9_L_BDMz1ybiGss09LLcM_egbitgj0FT5Nh9u4,245
|
68
|
+
testgen/util/z3_utils/z3_test_case.py,sha256=yF4oJOrXMLzOwDUqXdoeg83MOTl3pvc_lYaZcS01CuQ,4983
|
69
|
+
testgenie_py-0.3.9.dist-info/METADATA,sha256=B9Hz4soDJ1VUOEPrWy_gWCwaUjpZuU95lgCbRTNTKss,4350
|
70
|
+
testgenie_py-0.3.9.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
71
|
+
testgenie_py-0.3.9.dist-info/entry_points.txt,sha256=OUN4GqB4zHlHWwWGjwIPbur4E_ZqQgkeeqaCLhzRZgg,47
|
72
|
+
testgenie_py-0.3.9.dist-info/RECORD,,
|
testgen/inspector/inspector.py
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
from ast import FunctionType
|
2
|
-
import inspect
|
3
|
-
|
4
|
-
class Inspector:
|
5
|
-
|
6
|
-
@staticmethod
|
7
|
-
def get_functions(file):
|
8
|
-
return inspect.getmembers(file, inspect.isfunction)
|
9
|
-
|
10
|
-
@staticmethod
|
11
|
-
def get_signature(func: FunctionType):
|
12
|
-
return inspect.signature(func)
|
13
|
-
|
14
|
-
@staticmethod
|
15
|
-
def get_code(func: FunctionType):
|
16
|
-
return inspect.getsource(func)
|
17
|
-
|
18
|
-
@staticmethod
|
19
|
-
def get_params(sig: inspect.Signature):
|
20
|
-
return sig.parameters
|
21
|
-
|
22
|
-
@staticmethod
|
23
|
-
def get_params_not_self(sig: inspect.Signature):
|
24
|
-
params = sig.parameters
|
25
|
-
return [param for param, value in params.items() if param != 'self']
|
26
|
-
|
27
|
-
def func_inspect(self, function_name) -> list[tuple]:
|
28
|
-
test_cases: list[tuple] = []
|
29
|
-
|
30
|
-
functions = inspect.getmembers(function_name, inspect.isfunction)
|
31
|
-
|
32
|
-
for name, func in functions:
|
33
|
-
print(f"Function Name: {name}")
|
34
|
-
signature = inspect.signature(func)
|
35
|
-
print(f"Signature: {signature}")
|
36
|
-
for param in signature.parameters:
|
37
|
-
print(f"Param: {param}")
|
38
|
-
print(f"Function Code: {inspect.getsource(func)}")
|
39
|
-
|
40
|
-
docstring: str = inspect.getdoc(func)
|
41
|
-
print(f"Docstring: {docstring}")
|
42
|
-
|
43
|
-
cases: list[str] = docstring.split(",")
|
44
|
-
|
45
|
-
for case in cases:
|
46
|
-
io: list[str] = case.split("-")
|
47
|
-
input: str = io[0][7:].strip()
|
48
|
-
output: str = io[1][8:].strip()
|
49
|
-
print(f"Input: {input}")
|
50
|
-
print(f"Output: {output}")
|
51
|
-
test_cases.append((name, (input, output)))
|
52
|
-
|
53
|
-
return test_cases
|
54
|
-
|
55
|
-
|
56
|
-
if __name__ == "__main__":
|
57
|
-
inspector = Inspector()
|
58
|
-
test_cases = inspector.func_inspect()
|
59
|
-
print(f"Collected Test Cases: {test_cases}")
|
testgen/presentation/__init__.py
DELETED
File without changes
|
testgen/presentation/cli_view.py
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
class CLIView:
|
2
|
-
def __init__(self):
|
3
|
-
"pass"
|
4
|
-
|
5
|
-
def display_message(self, message: str):
|
6
|
-
print(f"[INFO] {message}")
|
7
|
-
|
8
|
-
def display_error(self, error: str):
|
9
|
-
print(f"[ERROR] {error}")
|
10
|
-
|
11
|
-
def prompt_input(self, prompt: str) -> str:
|
12
|
-
return input(f"{prompt}:> ")
|
testgen/sqlite/__init__.py
DELETED
File without changes
|
testgen/sqlite/db_service.py
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import sqlite3
|
3
|
-
import json
|
4
|
-
import time
|
5
|
-
import ast
|
6
|
-
from datetime import datetime
|
7
|
-
from typing import List, Tuple
|
8
|
-
|
9
|
-
from testgen.models.test_case import TestCase
|
10
|
-
from testgen.sqlite.db import create_database
|
11
|
-
|
12
|
-
class DBService:
|
13
|
-
def __init__(self, db_name="testgen.db"):
|
14
|
-
self.db_name = db_name
|
15
|
-
self.conn = None
|
16
|
-
self.cursor = None
|
17
|
-
self._connect()
|
18
|
-
|
19
|
-
def _connect(self):
|
20
|
-
"""Establish connection to the database."""
|
21
|
-
if not os.path.exists(self.db_name):
|
22
|
-
create_database(self.db_name)
|
23
|
-
|
24
|
-
self.conn = sqlite3.connect(self.db_name)
|
25
|
-
self.conn.row_factory = sqlite3.Row
|
26
|
-
self.cursor = self.conn.cursor()
|
27
|
-
# Enable foreign keys
|
28
|
-
self.cursor.execute("PRAGMA foreign_keys = ON;")
|
29
|
-
|
30
|
-
def close(self):
|
31
|
-
"""Close the database connection."""
|
32
|
-
if self.conn:
|
33
|
-
self.conn.close()
|
34
|
-
self.conn = None
|
35
|
-
self.cursor = None
|
36
|
-
|
37
|
-
def insert_test_suite(self, name: str) -> int:
|
38
|
-
"""Insert a test suite and return its ID."""
|
39
|
-
self.cursor.execute(
|
40
|
-
"INSERT INTO TestSuite (name, creation_date) VALUES (?, ?)",
|
41
|
-
(name, datetime.now())
|
42
|
-
)
|
43
|
-
self.conn.commit()
|
44
|
-
return self.cursor.lastrowid
|
45
|
-
|
46
|
-
def insert_source_file(self, path: str, lines_of_code: int) -> int:
|
47
|
-
"""Insert a source file and return its ID."""
|
48
|
-
# Check if file already exists
|
49
|
-
self.cursor.execute("SELECT id FROM SourceFile WHERE path = ?", (path,))
|
50
|
-
existing = self.cursor.fetchone()
|
51
|
-
if existing:
|
52
|
-
return existing[0]
|
53
|
-
|
54
|
-
self.cursor.execute(
|
55
|
-
"INSERT INTO SourceFile (path, lines_of_code, last_modified) VALUES (?, ?, ?)",
|
56
|
-
(path, lines_of_code, datetime.now())
|
57
|
-
)
|
58
|
-
self.conn.commit()
|
59
|
-
return self.cursor.lastrowid
|
60
|
-
|
61
|
-
def insert_function(self, name: str, start_line: int, end_line: int, source_file_id: int) -> int:
|
62
|
-
"""Insert a function and return its ID."""
|
63
|
-
num_lines = end_line - start_line + 1
|
64
|
-
|
65
|
-
# Check if function already exists for this source file
|
66
|
-
self.cursor.execute(
|
67
|
-
"SELECT id FROM Function WHERE name = ? AND source_file_id = ?",
|
68
|
-
(name, source_file_id)
|
69
|
-
)
|
70
|
-
existing = self.cursor.fetchone()
|
71
|
-
if existing:
|
72
|
-
return existing[0]
|
73
|
-
|
74
|
-
self.cursor.execute(
|
75
|
-
"INSERT INTO Function (name, start_line, end_line, num_lines, source_file_id) VALUES (?, ?, ?, ?, ?)",
|
76
|
-
(name, start_line, end_line, num_lines, source_file_id)
|
77
|
-
)
|
78
|
-
self.conn.commit()
|
79
|
-
return self.cursor.lastrowid
|
80
|
-
|
81
|
-
def insert_test_case(self, test_case: TestCase, test_suite_id: int, function_id: int, test_method_type: int) -> int:
|
82
|
-
"""Insert a test case and return its ID."""
|
83
|
-
# Convert inputs and expected output to JSON strings
|
84
|
-
inputs_json = json.dumps(test_case.inputs)
|
85
|
-
expected_json = json.dumps(test_case.expected)
|
86
|
-
|
87
|
-
self.cursor.execute(
|
88
|
-
"INSERT INTO TestCase (name, expected_output, input, test_function, last_run_time, test_method_type, test_suite_id, function_id) "
|
89
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
90
|
-
(
|
91
|
-
f"test_{test_case.func_name}",
|
92
|
-
expected_json,
|
93
|
-
inputs_json,
|
94
|
-
test_case.func_name,
|
95
|
-
datetime.now(),
|
96
|
-
test_method_type,
|
97
|
-
test_suite_id,
|
98
|
-
function_id
|
99
|
-
)
|
100
|
-
)
|
101
|
-
self.conn.commit()
|
102
|
-
return self.cursor.lastrowid
|
103
|
-
|
104
|
-
def insert_test_result(self, test_case_id: int, status: bool, error: str = None) -> int:
|
105
|
-
"""Insert a test result and return its ID."""
|
106
|
-
self.cursor.execute(
|
107
|
-
"INSERT INTO TestResult (test_case_id, status, error, execution_time) VALUES (?, ?, ?, ?)",
|
108
|
-
(test_case_id, status, error, datetime.now())
|
109
|
-
)
|
110
|
-
self.conn.commit()
|
111
|
-
return self.cursor.lastrowid
|
112
|
-
|
113
|
-
def insert_coverage_data(self, file_name: str, executed_lines: str, missed_lines: str,
|
114
|
-
branch_coverage: float, source_file_id: int) -> int:
|
115
|
-
"""Insert coverage data and return its ID."""
|
116
|
-
self.cursor.execute(
|
117
|
-
"INSERT INTO CoverageData (file_name, executed_lines, missed_lines, branch_coverage, source_file_id) "
|
118
|
-
"VALUES (?, ?, ?, ?, ?)",
|
119
|
-
(file_name, executed_lines, missed_lines, branch_coverage, source_file_id)
|
120
|
-
)
|
121
|
-
self.conn.commit()
|
122
|
-
return self.cursor.lastrowid
|
123
|
-
|
124
|
-
def save_test_generation_data(self, file_path: str, test_cases: List[TestCase],
|
125
|
-
test_method_type: int, class_name: str = None) -> Tuple[int, List[int]]:
|
126
|
-
"""
|
127
|
-
Save all data related to a test generation run.
|
128
|
-
Returns the test suite ID and a list of test case IDs.
|
129
|
-
"""
|
130
|
-
# Count lines in the source file
|
131
|
-
with open(file_path, 'r') as f:
|
132
|
-
lines_of_code = len(f.readlines())
|
133
|
-
|
134
|
-
# Create test suite
|
135
|
-
strategy_names = {1: "AST", 2: "Fuzz", 3: "Random", 4: "Reinforcement"}
|
136
|
-
suite_name = f"{strategy_names.get(test_method_type, 'Unknown')}_Suite_{int(time.time())}"
|
137
|
-
test_suite_id = self.insert_test_suite(suite_name)
|
138
|
-
|
139
|
-
# Insert source file
|
140
|
-
source_file_id = self.insert_source_file(file_path, lines_of_code)
|
141
|
-
|
142
|
-
# Process functions and test cases
|
143
|
-
test_case_ids = []
|
144
|
-
function_ids = {} # Cache function IDs to avoid redundant queries
|
145
|
-
|
146
|
-
for test_case in test_cases:
|
147
|
-
# Extract function name from test case
|
148
|
-
func_name = test_case.func_name
|
149
|
-
|
150
|
-
# If function not already processed
|
151
|
-
if func_name not in function_ids:
|
152
|
-
# Get function line numbers
|
153
|
-
start_line, end_line = self._get_function_line_numbers(file_path, func_name)
|
154
|
-
function_id = self.insert_function(func_name, start_line, end_line, source_file_id)
|
155
|
-
function_ids[func_name] = function_id
|
156
|
-
|
157
|
-
# Insert test case
|
158
|
-
test_case_id = self.insert_test_case(
|
159
|
-
test_case,
|
160
|
-
test_suite_id,
|
161
|
-
function_ids[func_name],
|
162
|
-
test_method_type
|
163
|
-
)
|
164
|
-
test_case_ids.append(test_case_id)
|
165
|
-
|
166
|
-
return test_suite_id, test_case_ids
|
167
|
-
|
168
|
-
def _get_function_line_numbers(self, file_path: str, function_name: str) -> Tuple[int, int]:
|
169
|
-
"""
|
170
|
-
Extract the start and end line numbers for a function in a file.
|
171
|
-
Returns a tuple of (start_line, end_line).
|
172
|
-
"""
|
173
|
-
try:
|
174
|
-
# Load the file and parse it
|
175
|
-
with open(file_path, 'r') as f:
|
176
|
-
file_content = f.read()
|
177
|
-
|
178
|
-
tree = ast.parse(file_content)
|
179
|
-
|
180
|
-
# Find the function definition
|
181
|
-
for node in ast.walk(tree):
|
182
|
-
if isinstance(node, ast.FunctionDef) and node.name == function_name:
|
183
|
-
end_line = node.end_lineno if hasattr(node, 'end_lineno') else node.lineno + 5 # Estimate if end_lineno not available
|
184
|
-
return node.lineno, end_line
|
185
|
-
|
186
|
-
# Also look for class methods
|
187
|
-
for node in ast.walk(tree):
|
188
|
-
if isinstance(node, ast.ClassDef):
|
189
|
-
for class_node in node.body:
|
190
|
-
if isinstance(class_node, ast.FunctionDef) and class_node.name == function_name:
|
191
|
-
end_line = class_node.end_lineno if hasattr(class_node, 'end_lineno') else class_node.lineno + 5
|
192
|
-
return class_node.lineno, end_line
|
193
|
-
except Exception as e:
|
194
|
-
print(f"Error getting function line numbers: {e}")
|
195
|
-
|
196
|
-
# If we reach here, the function wasn't found or there was an error
|
197
|
-
return 0, 0
|
198
|
-
|
199
|
-
def get_test_suites(self):
|
200
|
-
"""Get all test suites from the database."""
|
201
|
-
self.cursor.execute("SELECT * FROM TestSuite ORDER BY creation_date DESC")
|
202
|
-
return self.cursor.fetchall()
|
203
|
-
|
204
|
-
def get_test_cases_by_function(self, function_name):
|
205
|
-
"""Get all test cases for a specific function."""
|
206
|
-
self.cursor.execute(
|
207
|
-
"SELECT tc.* FROM TestCase tc JOIN Function f ON tc.function_id = f.id WHERE f.name = ?",
|
208
|
-
(function_name,)
|
209
|
-
)
|
210
|
-
return self.cursor.fetchall()
|
211
|
-
|
212
|
-
def get_coverage_by_file(self, file_path):
|
213
|
-
"""Get coverage data for a specific file."""
|
214
|
-
self.cursor.execute(
|
215
|
-
"SELECT cd.* FROM CoverageData cd JOIN SourceFile sf ON cd.source_file_id = sf.id WHERE sf.path = ?",
|
216
|
-
(file_path,)
|
217
|
-
)
|
218
|
-
return self.cursor.fetchall()
|
219
|
-
|
220
|
-
def get_test_file_data(self, file_path: str):
|
221
|
-
"""
|
222
|
-
Retrieve all test cases, coverage data, and test results for a specific file.
|
223
|
-
"""
|
224
|
-
query = """
|
225
|
-
SELECT
|
226
|
-
tc.id AS test_case_id,
|
227
|
-
tc.name AS test_case_name,
|
228
|
-
tc.test_function AS test_case_test_function,
|
229
|
-
tc.test_method_type AS test_case_method_type,
|
230
|
-
COALESCE(cd.missed_lines, 'None') AS coverage_data_missed_lines
|
231
|
-
FROM SourceFile sf
|
232
|
-
LEFT JOIN Function f ON sf.id = f.source_file_id
|
233
|
-
LEFT JOIN TestCase tc ON f.id = tc.function_id
|
234
|
-
LEFT JOIN TestResult tr ON tc.id = tr.test_case_id
|
235
|
-
LEFT JOIN CoverageData cd ON sf.id = cd.source_file_id
|
236
|
-
WHERE sf.path = ?;
|
237
|
-
"""
|
238
|
-
self.cursor.execute(query, (file_path,))
|
239
|
-
return self.cursor.fetchall()
|
testgen/testgen.db
DELETED
Binary file
|