testgenie-py 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (82) hide show
  1. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/PKG-INFO +2 -1
  2. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/pyproject.toml +2 -1
  3. testgenie_py-0.2.2/testgen/analyzer/random_feedback_analyzer.py +521 -0
  4. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/controller/cli_controller.py +5 -10
  5. testgenie_py-0.2.2/testgen/generated_samplecodebin.py +545 -0
  6. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/code_generator.py +36 -18
  7. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/reinforcement/environment.py +1 -1
  8. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/service/generator_service.py +16 -1
  9. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/service/service.py +124 -32
  10. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/sqlite/db_service.py +22 -2
  11. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/coverage_utils.py +35 -0
  12. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/randomizer.py +29 -12
  13. testgenie_py-0.2.1/testgen/.coverage +0 -0
  14. testgenie_py-0.2.1/testgen/analyzer/random_feedback_analyzer.py +0 -291
  15. testgenie_py-0.2.1/testgen/code_to_test/boolean.py +0 -146
  16. testgenie_py-0.2.1/testgen/code_to_test/calculator.py +0 -29
  17. testgenie_py-0.2.1/testgen/code_to_test/code_to_fuzz.py +0 -234
  18. testgenie_py-0.2.1/testgen/code_to_test/code_to_fuzz_lite.py +0 -397
  19. testgenie_py-0.2.1/testgen/code_to_test/decisions.py +0 -57
  20. testgenie_py-0.2.1/testgen/code_to_test/math_utils.py +0 -47
  21. testgenie_py-0.2.1/testgen/code_to_test/no_types.py +0 -35
  22. testgenie_py-0.2.1/testgen/code_to_test/sample_code_bin.py +0 -141
  23. testgenie_py-0.2.1/testgen/q_table/global_q_table.json +0 -1
  24. testgenie_py-0.2.1/testgen/testgen.db +0 -0
  25. testgenie_py-0.2.1/testgen/tests/test_boolean.py +0 -69
  26. testgenie_py-0.2.1/testgen/tests/test_decisions.py +0 -195
  27. testgenie_py-0.2.1/testgen/util/__init__.py +0 -0
  28. testgenie_py-0.2.1/testgen/util/z3_utils/__init__.py +0 -0
  29. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/README.md +0 -0
  30. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/__init__.py +0 -0
  31. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/__init__.py +0 -0
  32. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/ast_analyzer.py +0 -0
  33. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/contracts/__init__.py +0 -0
  34. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/contracts/contract.py +0 -0
  35. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/contracts/no_exception_contract.py +0 -0
  36. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/contracts/nonnull_contract.py +0 -0
  37. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/fuzz_analyzer.py +0 -0
  38. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/reinforcement_analyzer.py +0 -0
  39. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/test_case_analyzer.py +0 -0
  40. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/analyzer/test_case_analyzer_context.py +0 -0
  41. {testgenie_py-0.2.1/testgen/code_to_test → testgenie_py-0.2.2/testgen/controller}/__init__.py +0 -0
  42. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/controller/docker_controller.py +0 -0
  43. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/docker/Dockerfile +0 -0
  44. {testgenie_py-0.2.1/testgen/controller → testgenie_py-0.2.2/testgen/generator}/__init__.py +0 -0
  45. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/doctest_generator.py +0 -0
  46. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/generator.py +0 -0
  47. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/pytest_generator.py +0 -0
  48. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/test_generator.py +0 -0
  49. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/generator/unit_test_generator.py +0 -0
  50. {testgenie_py-0.2.1/testgen/generator → testgenie_py-0.2.2/testgen/inspector}/__init__.py +0 -0
  51. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/inspector/inspector.py +0 -0
  52. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/main.py +0 -0
  53. {testgenie_py-0.2.1/testgen/inspector → testgenie_py-0.2.2/testgen/models}/__init__.py +0 -0
  54. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/models/analysis_context.py +0 -0
  55. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/models/function_metadata.py +0 -0
  56. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/models/generator_context.py +0 -0
  57. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/models/test_case.py +0 -0
  58. {testgenie_py-0.2.1/testgen/models → testgenie_py-0.2.2/testgen/presentation}/__init__.py +0 -0
  59. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/presentation/cli_view.py +0 -0
  60. {testgenie_py-0.2.1/testgen/presentation → testgenie_py-0.2.2/testgen/reinforcement}/__init__.py +0 -0
  61. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/reinforcement/abstract_state.py +0 -0
  62. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/reinforcement/agent.py +0 -0
  63. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/reinforcement/statement_coverage_state.py +0 -0
  64. {testgenie_py-0.2.1/testgen/reinforcement → testgenie_py-0.2.2/testgen/service}/__init__.py +0 -0
  65. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/service/analysis_service.py +0 -0
  66. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/service/cfg_service.py +0 -0
  67. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/service/logging_service.py +0 -0
  68. {testgenie_py-0.2.1/testgen/service → testgenie_py-0.2.2/testgen/sqlite}/__init__.py +0 -0
  69. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/sqlite/db.py +0 -0
  70. {testgenie_py-0.2.1/testgen/sqlite → testgenie_py-0.2.2/testgen/tree}/__init__.py +0 -0
  71. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/tree/node.py +0 -0
  72. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/tree/tree_utils.py +0 -0
  73. {testgenie_py-0.2.1/testgen/tests → testgenie_py-0.2.2/testgen/util}/__init__.py +0 -0
  74. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/coverage_visualizer.py +0 -0
  75. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/file_utils.py +0 -0
  76. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/utils.py +0 -0
  77. {testgenie_py-0.2.1/testgen/tree → testgenie_py-0.2.2/testgen/util/z3_utils}/__init__.py +0 -0
  78. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/z3_utils/ast_to_z3.py +0 -0
  79. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/z3_utils/branch_condition.py +0 -0
  80. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/z3_utils/constraint_extractor.py +0 -0
  81. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/z3_utils/variable_finder.py +0 -0
  82. {testgenie_py-0.2.1 → testgenie_py-0.2.2}/testgen/util/z3_utils/z3_test_case.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: testgenie-py
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary:
5
5
  Author: cjseitz
6
6
  Author-email: charlesjseitz@gmail.com
@@ -19,6 +19,7 @@ Requires-Dist: klara (==0.6.3)
19
19
  Requires-Dist: pygraphviz (>=1.14,<2.0)
20
20
  Requires-Dist: pytest (>=8.3.5,<9.0.0)
21
21
  Requires-Dist: staticfg (>=0.9.5,<0.10.0)
22
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
22
23
  Requires-Dist: typed-ast (==1.5.5)
23
24
  Requires-Dist: z3-solver (==4.13.3.0)
24
25
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "testgenie-py"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = ""
5
5
  authors = ["cjseitz <charlesjseitz@gmail.com>"]
6
6
  readme = "README.md"
@@ -25,6 +25,7 @@ staticfg = "^0.9.5"
25
25
  pytest = "^8.3.5"
26
26
  pygraphviz = "^1.14"
27
27
  docker = "^7.1.0"
28
+ tabulate = "^0.9.0"
28
29
 
29
30
  [build-system]
30
31
  requires = ["poetry-core"]
@@ -0,0 +1,521 @@
1
+ import ast
2
+ import importlib
3
+ import random
4
+ import time
5
+ import traceback
6
+ from typing import List, Dict, Set
7
+ import z3
8
+
9
+ import testgen.util.randomizer
10
+ import testgen.util.utils as utils
11
+ import testgen.util.coverage_utils as coverage_utils
12
+ from testgen.analyzer.contracts.contract import Contract
13
+ from testgen.analyzer.contracts.no_exception_contract import NoExceptionContract
14
+ from testgen.analyzer.contracts.nonnull_contract import NonNullContract
15
+ from testgen.models.test_case import TestCase
16
+ from testgen.analyzer.test_case_analyzer import TestCaseAnalyzerStrategy
17
+ from abc import ABC
18
+
19
+ from testgen.models.function_metadata import FunctionMetadata
20
+ from testgen.util.z3_utils.constraint_extractor import extract_branch_conditions
21
+ from testgen.util.z3_utils.ast_to_z3 import ast_to_z3_constraint
22
+
23
+
24
+ # Citation in which this method and algorithm were taken from:
25
+ # C. Pacheco, S. K. Lahiri, M. D. Ernst and T. Ball, "Feedback-Directed Random Test Generation," 29th International
26
+ # Conference on Software Engineering (ICSE'07), Minneapolis, MN, USA, 2007, pp. 75-84, doi: 10.1109/ICSE.2007.37.
27
+ # keywords: {System testing;Contracts;Object oriented modeling;Law;Legal factors;Open source software;Software
28
+ # testing;Feedback;Filters;Error correction codes},
29
+
30
+ class RandomFeedbackAnalyzer(TestCaseAnalyzerStrategy, ABC):
31
+ def __init__(self, analysis_context=None):
32
+ super().__init__(analysis_context)
33
+ self.test_cases = []
34
+ self.covered_lines: Dict[str, Set[int]] = {}
35
+ self.covered_functions: Set[str] = set()
36
+
37
+ def collect_test_cases(self, function_metadata: FunctionMetadata, time_limit: int = 5) -> List[TestCase]:
38
+ self.test_cases = []
39
+ start_time = time.time()
40
+
41
+ while (time.time() - start_time) < time_limit:
42
+
43
+ try:
44
+ param_values = self.generate_random_inputs(function_metadata.params)
45
+ module = self.analysis_context.module
46
+ func_name = function_metadata.function_name
47
+
48
+ if self._analysis_context.class_name:
49
+ cls = getattr(module, self._analysis_context.class_name)
50
+ obj = cls()
51
+ function = getattr(obj, func_name)
52
+ else:
53
+ function = getattr(module, func_name)
54
+
55
+ import inspect
56
+ sig = inspect.signature(function)
57
+ param_names = [p.name for p in sig.parameters.values() if p.name != 'self']
58
+
59
+ ordered_args = []
60
+ for name in param_names:
61
+ if name in param_values:
62
+ ordered_args.append(param_values[name])
63
+ else:
64
+ ordered_args.append(None)
65
+
66
+ result = function(*ordered_args)
67
+ test_case = TestCase(func_name, tuple(ordered_args), result)
68
+
69
+ if not self.is_duplicate_test_case(test_case):
70
+ self.test_cases.append(test_case)
71
+
72
+ covered = self.covered(function_metadata)
73
+ if covered:
74
+ break
75
+ else:
76
+ # Optionally log duplicate detection
77
+ print(f"Skipping duplicate test case: {func_name}{test_case.inputs}")
78
+
79
+ except Exception as e:
80
+ print(f"Error testing {function_metadata.function_name}: {e}")
81
+
82
+ return self.test_cases
83
+
84
+ def is_duplicate_test_case(self, new_test_case: TestCase) -> bool:
85
+ for existing_test_case in self.test_cases:
86
+ if (existing_test_case.func_name == new_test_case.func_name and
87
+ existing_test_case.inputs == new_test_case.inputs):
88
+ return True
89
+ return False
90
+
91
+ def covered(self, func: FunctionMetadata) -> bool:
92
+ if func.function_name not in self.covered_lines:
93
+ self.covered_lines[func.function_name] = set()
94
+
95
+ for test_case in [tc for tc in self.test_cases if tc.func_name == func.function_name]:
96
+ analysis = coverage_utils.get_coverage_analysis(self._analysis_context.filepath, self._analysis_context.class_name,
97
+ func.function_name, test_case.inputs)
98
+ covered = coverage_utils.get_list_of_covered_statements(analysis)
99
+ self.covered_lines[func.function_name].update(covered)
100
+ print(f"Covered lines for {func.function_name}: {self.covered_lines[func.function_name]}")
101
+
102
+ executable_statements = set(self.get_all_executable_statements(func))
103
+ print(f"Executable statements for {func.function_name}: {executable_statements}")
104
+
105
+ return self.covered_lines[func.function_name] == executable_statements
106
+
107
+ def execute_sequence(self, sequence, contracts: List[Contract]):
108
+ """Execute a sequence and check contract violations"""
109
+ func_name, args_dict = sequence
110
+ args = tuple(args_dict.values()) # Convert dict values to tuple
111
+
112
+ try:
113
+ # Use module from analysis context if available
114
+ module = self.analysis_context.module
115
+
116
+ if self._analysis_context.class_name:
117
+ cls = getattr(module, self._analysis_context.class_name, None)
118
+ if cls is None:
119
+ raise AttributeError(f"Class '{self._analysis_context.class_name}' not found")
120
+ obj = cls() # Instantiate the class
121
+ func = getattr(obj, func_name, None)
122
+
123
+ import inspect
124
+ sig = inspect.signature(func)
125
+ param_names = [p.name for p in sig.parameters.values() if p.name != 'self']
126
+ else:
127
+ func = getattr(module, func_name, None)
128
+
129
+ import inspect
130
+ sig = inspect.signature(func)
131
+ param_names = [p.name for p in sig.parameters.values()]
132
+
133
+ # Create ordered arguments based on function signature
134
+ ordered_args = []
135
+ for name in param_names:
136
+ if name in args_dict:
137
+ ordered_args.append(args_dict[name])
138
+
139
+ # Check preconditions
140
+ for contract in contracts:
141
+ if not contract.check_preconditions(tuple(ordered_args)):
142
+ print(f"Preconditions failed for {func_name} with {tuple(ordered_args)}")
143
+ return None, True
144
+
145
+ # Execute function with properly ordered arguments
146
+ output = func(*ordered_args)
147
+ exception = None
148
+
149
+ except Exception as e:
150
+ print(f"EXCEPTION IN RANDOM FEEDBACK: {e}")
151
+ print(traceback.format_exc())
152
+ output = None
153
+ exception = e
154
+
155
+ # Check postconditions
156
+ for contract in contracts:
157
+ if not contract.check_postconditions(tuple(ordered_args), output, exception):
158
+ print(f"Postcondition failed for {func_name} with {tuple(ordered_args)}")
159
+ return output, True
160
+
161
+ return output, False
162
+
163
+
164
+ # TODO: Currently only getting random vals of primitives, extend to sequences
165
+ def random_seqs_and_vals(self, param_types, non_error_seqs=None):
166
+ return self.generate_random_inputs(param_types)
167
+
168
+ @staticmethod
169
+ def extract_parameter_types(func_node):
170
+ """Extract parameter types from a function node."""
171
+ param_types = {}
172
+ for arg in func_node.args.args:
173
+ param_name = arg.arg
174
+ if arg.annotation:
175
+ param_type = ast.unparse(arg.annotation)
176
+ param_types[param_name] = param_type
177
+ else:
178
+ if param_name != 'self':
179
+ param_types[param_name] = None
180
+ return param_types
181
+
182
+ @staticmethod
183
+ def generate_random_inputs(param_types):
184
+ """Generate inputs for fuzzing based on parameter types."""
185
+ inputs = {}
186
+ for param, param_type in param_types.items():
187
+ if param_type == "int":
188
+ random_integer = random.randint(-500, 500) # Wider range for better edge cases
189
+ inputs[param] = random_integer
190
+ elif param_type == "bool":
191
+ random_choice = random.choice([True, False])
192
+ inputs[param] = random_choice
193
+ elif param_type == "float":
194
+ random_float = random.uniform(-500.0, 500.0) # Wider range for better edge cases
195
+ inputs[param] = random_float
196
+ elif param_type == "str":
197
+ # Generate diverse strings instead of always "abc"
198
+ string_type = random.choice([
199
+ "empty", "short", "medium", "long", "special", "numeric", "whitespace"
200
+ ])
201
+
202
+ if string_type == "empty":
203
+ inputs[param] = ""
204
+ elif string_type == "short":
205
+ inputs[param] = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=random.randint(1, 3)))
206
+ elif string_type == "medium":
207
+ inputs[param] = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', k=random.randint(4, 10)))
208
+ elif string_type == "long":
209
+ inputs[param] = ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=random.randint(11, 30)))
210
+ elif string_type == "special":
211
+ inputs[param] = ''.join(random.choices('!@#$%^&*()_+-=[]{}|;:,./<>?', k=random.randint(1, 8)))
212
+ elif string_type == "numeric":
213
+ inputs[param] = ''.join(random.choices('0123456789', k=random.randint(1, 10)))
214
+ else: # whitespace
215
+ inputs[param] = ' ' * random.randint(1, 5)
216
+ else:
217
+ # For unknown types, try a default value
218
+ inputs[param] = None
219
+
220
+ return inputs
221
+
222
+ # Algorithm described in above article
223
+ # Classes is the classes for which we want to generate sequences
224
+ # Contracts express invariant properties that hold both at entry and exit from a call
225
+ # Contract takes as input the current state of the system (runtime values created in the sequence so far, and any exception thrown by the last call), and returns satisfied or violated
226
+ # Output is the runtime values and boolean flag violated
227
+ # Filters determine which values of a sequence are extensible and should be used as inputs
228
+ def generate_sequences(self, function_metadata: List[FunctionMetadata], classes=None, contracts: List[Contract] = None, filters=None, time_limit=20):
229
+ contracts = [NonNullContract(), NoExceptionContract()]
230
+ error_seqs = [] # execution violates a contract
231
+ non_error_seqs = [] # execution does not violate a contract
232
+
233
+ functions = self._analysis_context.function_data
234
+ start_time = time.time()
235
+ while(time.time() - start_time) >= time_limit:
236
+ # Get random function
237
+ func = random.choice(functions)
238
+ param_types: dict = func.params
239
+ vals: dict = self.random_seqs_and_vals(param_types)
240
+ new_seq = (func.function_name, vals)
241
+ if new_seq in error_seqs or new_seq in non_error_seqs:
242
+ continue
243
+ outs_violated: tuple = self.execute_sequence(new_seq, contracts)
244
+ violated: bool = outs_violated[1]
245
+ # Create tuple of sequence ((func name, args), output)
246
+ new_seq_out = (new_seq, outs_violated[0])
247
+ if violated:
248
+ error_seqs.append(new_seq_out)
249
+ else:
250
+ # Question: Should I use the failed contract to be the assertion in unit test??
251
+ non_error_seqs.append(new_seq_out)
252
+ return error_seqs, non_error_seqs
253
+
254
+ def generate_sequences_new(self, contracts: List[Contract] = None, filters=None, time_limit=20):
255
+ contracts = [NonNullContract(), NoExceptionContract()]
256
+ error_seqs = [] # execution violates a contract
257
+ non_error_seqs = [] # execution does not violate a contract
258
+
259
+ functions = self._analysis_context.function_data.copy()
260
+ start_time = time.time()
261
+
262
+ while (time.time() - start_time) < time_limit:
263
+ # Get random function
264
+ func = random.choice(functions)
265
+ param_types: dict = func.params
266
+ vals: dict = self.random_seqs_and_vals(param_types)
267
+ new_seq = (func.function_name, vals)
268
+
269
+ if new_seq in [seq[0] for seq in error_seqs] or new_seq in [seq[0] for seq in non_error_seqs]:
270
+ continue
271
+
272
+ outs_violated: tuple = self.execute_sequence(new_seq, contracts)
273
+ violated: bool = outs_violated[1]
274
+
275
+ # Create tuple of sequence ((func name, args), output)
276
+ new_seq_out = (new_seq, outs_violated[0])
277
+
278
+ if violated:
279
+ error_seqs.append(new_seq_out)
280
+
281
+ else:
282
+ non_error_seqs.append(new_seq_out)
283
+
284
+ test_case = TestCase(new_seq_out[0][0], tuple(new_seq_out[0][1].values()), new_seq_out[1])
285
+ self.test_cases.append(test_case)
286
+ fully_covered = self.covered(func)
287
+ if fully_covered:
288
+ print(f"Function {func.function_name} is fully covered")
289
+ functions.remove(func)
290
+
291
+ if not functions:
292
+ self.test_cases.sort(key=lambda tc: tc.func_name)
293
+ print("All functions covered")
294
+ break
295
+
296
+ self.test_cases.sort(key=lambda tc: tc.func_name)
297
+ return error_seqs, non_error_seqs
298
+
299
+ def get_all_executable_statements(self, func: FunctionMetadata):
300
+ import ast
301
+
302
+ test_cases = [tc for tc in self.test_cases if tc.func_name == func.function_name]
303
+
304
+ if not test_cases:
305
+ print("Warning: No test cases available to determine executable statements")
306
+ from testgen.util.randomizer import new_random_test_case
307
+ temp_case = new_random_test_case(self._analysis_context.filepath, func.func_def)
308
+ analysis = coverage_utils.get_coverage_analysis(self._analysis_context.filepath, self._analysis_context.class_name, func.function_name,
309
+ temp_case.inputs)
310
+ else:
311
+ analysis = coverage_utils.get_coverage_analysis(self._analysis_context.filepath, self._analysis_context.class_name, func.function_name, test_cases[0].inputs)
312
+
313
+ executable_lines = list(analysis[1])
314
+
315
+ with open(self._analysis_context.filepath, 'r') as f:
316
+ source = f.read()
317
+
318
+ tree = ast.parse(source)
319
+
320
+ for node in ast.walk(tree):
321
+ if isinstance(node, ast.FunctionDef) and node.name == func.func_def.name:
322
+ for if_node in ast.walk(node):
323
+ if isinstance(if_node, ast.If) and if_node.orelse:
324
+ if isinstance(if_node.orelse[0], ast.If):
325
+ continue
326
+ else_line = if_node.orelse[0].lineno - 1
327
+
328
+ with open(self._analysis_context.filepath, 'r') as f:
329
+ lines = f.readlines()
330
+ if else_line <= len(lines):
331
+ line_content = lines[else_line - 1].strip()
332
+ if line_content == "else:":
333
+ if else_line not in executable_lines:
334
+ executable_lines.append(else_line)
335
+
336
+ return sorted(executable_lines)
337
+
338
+ """
339
+ def collect_test_cases_with_z3(self, function_metadata: FunctionMetadata) -> List[TestCase]:
340
+ test_cases = []
341
+
342
+ z3_test_cases = self.generate_z3_test_cases(function_metadata)
343
+ if z3_test_cases:
344
+ test_cases.extend(z3_test_cases)
345
+
346
+ if not test_cases:
347
+ test_cases = self.generate_sequences_new()[1]
348
+
349
+ self.test_cases = test_cases
350
+ return test_cases
351
+
352
+ def generate_z3_test_cases(self, function_metadata: FunctionMetadata) -> List[TestCase]:
353
+ test_cases = []
354
+
355
+ branch_conditions, param_types = extract_branch_conditions(function_metadata.func_def)
356
+
357
+ if not branch_conditions:
358
+ random_inputs = self.generate_random_inputs(function_metadata.params)
359
+ try:
360
+ module = self.analysis_context.module
361
+ func_name = function_metadata.function_name
362
+
363
+ if self._analysis_context.class_name:
364
+ cls = getattr(module, self._analysis_context.class_name)
365
+ obj = cls()
366
+ func = getattr(obj, func_name)
367
+ ordered_args = self._order_arguments(func, random_inputs)
368
+ output = func(*ordered_args)
369
+ else:
370
+ func = getattr(module, func_name)
371
+ ordered_args = self._order_arguments(func, random_inputs)
372
+ output = func(*ordered_args)
373
+
374
+ test_cases.append(TestCase(func_name, tuple(ordered_args), output))
375
+ except Exception as e:
376
+ print(f"Error executing function with random inputs: {e}")
377
+
378
+ return test_cases
379
+
380
+ for branch_condition in branch_conditions:
381
+ try:
382
+ z3_expr, z3_vars = ast_to_z3_constraint(branch_condition, function_metadata.params)
383
+
384
+ solver = z3.Solver()
385
+ solver.add(z3_expr)
386
+
387
+ neg_solver = z3.Solver()
388
+ neg_solver.add(z3.Not(z3_expr))
389
+
390
+ for current_solver in [solver, neg_solver]:
391
+ if current_solver.check() == z3.sat:
392
+ model = current_solver.model()
393
+
394
+ param_values = self._extract_z3_solution(model, z3_vars, function_metadata.params)
395
+
396
+ ordered_params = self._order_parameters(function_metadata.func_def, param_values)
397
+
398
+ try:
399
+ module = self.analysis_context.module
400
+ func_name = function_metadata.function_name
401
+
402
+ if self._analysis_context.class_name:
403
+ cls = getattr(module, self._analysis_context.class_name)
404
+ obj = cls()
405
+ func = getattr(obj, func_name)
406
+ else:
407
+ func = getattr(module, func_name)
408
+
409
+ result = func(*ordered_params)
410
+ test_cases.append(TestCase(func_name, tuple(ordered_params), result))
411
+ except Exception as e:
412
+ print(f"Error executing function with Z3 solution: {e}")
413
+ self._add_random_test_case(function_metadata, test_cases)
414
+ else:
415
+ self._add_random_test_case(function_metadata, test_cases)
416
+
417
+ except Exception as e:
418
+ print(f"Error processing branch condition with Z3: {e}")
419
+ self._add_random_test_case(function_metadata, test_cases)
420
+
421
+ return test_cases
422
+
423
+ def _extract_z3_solution(self, model, z3_vars, param_types):
424
+ param_values = {}
425
+
426
+ for var_name, z3_var in z3_vars.items():
427
+ if var_name in param_types:
428
+ try:
429
+ model_value = model.evaluate(z3_var)
430
+
431
+ if param_types[var_name] == "int":
432
+ param_values[var_name] = model_value.as_long()
433
+ elif param_types[var_name] == "float":
434
+ param_values[var_name] = float(model_value.as_decimal(10))
435
+ elif param_types[var_name] == "bool":
436
+ param_values[var_name] = z3.is_true(model_value)
437
+ elif param_types[var_name] == "str":
438
+ str_val = str(model_value)
439
+ if str_val.startswith('"') and str_val.endswith('"'):
440
+ str_val = str_val[1:-1]
441
+ param_values[var_name] = str_val
442
+ else:
443
+ # Default to int for unrecognized types
444
+ param_values[var_name] = model_value.as_long()
445
+ except Exception as e:
446
+ print(f"Couldn't get {var_name} from model: {e}")
447
+ # Use default values for parameters not in the model
448
+ if param_types[var_name] == "int":
449
+ param_values[var_name] = 0
450
+ elif param_types[var_name] == "float":
451
+ param_values[var_name] = 0.0
452
+ elif param_types[var_name] == "bool":
453
+ param_values[var_name] = False
454
+ elif param_types[var_name] == "str":
455
+ param_values[var_name] = ""
456
+ else:
457
+ param_values[var_name] = None
458
+
459
+ return param_values
460
+
461
+ def _order_parameters(self, func_node, param_values):
462
+ ordered_params = []
463
+
464
+ for arg in func_node.args.args:
465
+ arg_name = arg.arg
466
+ if arg_name == 'self': # Skip self parameter
467
+ continue
468
+ if arg_name in param_values:
469
+ ordered_params.append(param_values[arg_name])
470
+ else:
471
+ # Default value handling if parameter not in solution
472
+ if arg.annotation and hasattr(arg.annotation, 'id'):
473
+ if arg.annotation.id == 'int':
474
+ ordered_params.append(0)
475
+ elif arg.annotation.id == 'float':
476
+ ordered_params.append(0.0)
477
+ elif arg.annotation.id == 'bool':
478
+ ordered_params.append(False)
479
+ elif arg.annotation.id == 'str':
480
+ ordered_params.append('')
481
+ else:
482
+ ordered_params.append(None)
483
+ else:
484
+ ordered_params.append(None)
485
+
486
+ return ordered_params
487
+
488
+ def _order_arguments(self, func, args_dict):
489
+ import inspect
490
+ sig = inspect.signature(func)
491
+ param_names = [p.name for p in sig.parameters.values() if p.name != 'self']
492
+
493
+ ordered_args = []
494
+ for name in param_names:
495
+ if name in args_dict:
496
+ ordered_args.append(args_dict[name])
497
+ else:
498
+ ordered_args.append(None) # Default to None if missing
499
+
500
+ return ordered_args
501
+
502
+ def _add_random_test_case(self, function_metadata, test_cases):
503
+ random_inputs = self.generate_random_inputs(function_metadata.params)
504
+ try:
505
+ module = self.analysis_context.module
506
+ func_name = function_metadata.function_name
507
+
508
+ if self._analysis_context.class_name:
509
+ cls = getattr(module, self._analysis_context.class_name)
510
+ obj = cls()
511
+ func = getattr(obj, func_name)
512
+ else:
513
+ func = getattr(module, func_name)
514
+
515
+ ordered_args = self._order_arguments(func, random_inputs)
516
+
517
+ output = func(*ordered_args)
518
+ test_cases.append(TestCase(func_name, tuple(ordered_args), output))
519
+ except Exception as e:
520
+ print(f"Error executing function with random inputs: {e}")
521
+ """
@@ -42,10 +42,9 @@ class CLIController:
42
42
 
43
43
  logger = get_logger()
44
44
 
45
- if args.select_all:
46
- self.view.display_message("Selecting all from SQLite database...")
47
- # Assuming you have a method in your service to handle this
48
- self.service.select_all_from_db()
45
+ if args.query:
46
+ print(f"Querying database for file: {args.file_path}")
47
+ self.service.query_test_file_data(args.file_path)
49
48
  return
50
49
 
51
50
  running_in_docker = os.environ.get("RUNNING_IN_DOCKER") is not None
@@ -111,6 +110,7 @@ class CLIController:
111
110
  parser = argparse.ArgumentParser(description="A CLI tool for generating unit tests.")
112
111
  parser.add_argument("file_path", type=str, help="Path to the Python file.")
113
112
  parser.add_argument("--output", "-o", type=str, help="Path to output directory.")
113
+ parser.add_argument("-q", "--query", action="store_true", help="Query the database for test cases, coverage data, and test results for a specific file")
114
114
  parser.add_argument(
115
115
  "--generate-only", "-g",
116
116
  action="store_true",
@@ -146,12 +146,7 @@ class CLIController:
146
146
  help="Path to SQLite database file (default: testgen.db)"
147
147
  )
148
148
  parser.add_argument(
149
- "--select-all",
150
- action="store_true",
151
- help="Select all from sqlite db"
152
- )
153
- parser.add_argument(
154
- "--visualize",
149
+ "-viz", "--visualize",
155
150
  action="store_true",
156
151
  help = "Visualize the tests with graphviz"
157
152
  )