testomaton 0.2.2__py2.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.
@@ -0,0 +1,238 @@
1
+ from testomaton.model import Function, OutputParameter
2
+ from testomaton.solver import TomatoSolver
3
+
4
+ from itertools import combinations, product
5
+ import sys
6
+ import copy
7
+ import csv
8
+ import itertools
9
+
10
+ def validate_headrow(headrow: list, function: Function):
11
+ messages = []
12
+ parameter_names = function.get_parameter_names()
13
+ valid = (headrow == parameter_names)
14
+ if not valid:
15
+ messages.append(f'Expected:\n{parameter_names}')
16
+ return messages, valid
17
+
18
+ def validate_test_by_choice_names(test: list, function: Function, solver: TomatoSolver):
19
+ messages = []
20
+ result = True
21
+
22
+ geninput = function.get_generator_input()
23
+ parameter_names = geninput[0]
24
+ parameters = [function.get_parameter(name) for name in parameter_names]
25
+ leaf_choices = geninput[1]
26
+
27
+ #first check if the test has the same number of values as the function has parameters
28
+ if len(test) != len(parameter_names):
29
+ result = False
30
+ messages.append(f'Number of values in the test ({len(test)}) does not match the number of parameters in the function ({len(parameter_names)})')
31
+ # print_exit_messages(messages, result)
32
+ return messages, result
33
+
34
+ #check if the values in the test are in the list of allowed values for the corresponding parameter
35
+ for i in range(len(test)):
36
+ parameter = parameters[i]
37
+ if isinstance(parameter, OutputParameter):
38
+ continue
39
+ if parameter.get_choice(test[i]) is None:
40
+ result = False
41
+ messages.append(f"Choice '{test[i]}' is not defined for parameter '{parameter_names[i]}'")
42
+
43
+ if not result:
44
+ # print_exit_messages(messages, result)
45
+ return messages, result
46
+
47
+ #test if the test case satisfies the constraints
48
+ test_copy = [test[i] if not isinstance(parameters[i], OutputParameter) else parameters[i].default_value for i in range(len(test))]
49
+ if not solver.test(test_copy):
50
+ result = False
51
+ messages.append(f'Test case does not satisfy the constraints')
52
+ # print_exit_messages(messages, result)
53
+ return messages, result
54
+
55
+ #test if the test case satisfies the assignments
56
+ test_copy = solver.adapt(test_copy)
57
+ for i in range(len(test)):
58
+ if test_copy[i] != test[i]:
59
+ result = False
60
+ messages.append(f"Value of parameter {parameter_names[i]} should be {test_copy[i]}")
61
+
62
+ # print_exit_messages(messages, result)
63
+ return messages, result
64
+
65
+ def validate_test_by_values(test: list, function: Function, solver: TomatoSolver):
66
+ def print_exit_messages(messages, success):
67
+ messages[0] = messages[0] + f'{VALID if success else INVALID}'
68
+ [print(msg) for msg in messages]
69
+
70
+ messages = []
71
+ result = True
72
+
73
+ geninput = function.get_generator_input()
74
+ parameter_names = geninput[0]
75
+ parameters = [function.get_parameter(name) for name in parameter_names]
76
+ leaf_choices = geninput[1]
77
+
78
+ #first check if the test has the same number of values as the function has parameters
79
+ if len(test) != len(parameter_names):
80
+ result = False
81
+ messages.append(f'Number of values in the test ({len(test)}) does not match the number of parameters in the function ({len(parameter_names)})')
82
+ # print_exit_messages(messages, result)
83
+ return messages, result
84
+
85
+ #check if the values in the test are in the list of allowed values for the corresponding parameter
86
+ for i in range(len(test)):
87
+ parameter = parameters[i]
88
+ full_name = parameter_names[i]
89
+ if isinstance(parameter, OutputParameter):
90
+ continue
91
+ leaf_choices[i] = choices = [choice for choice in leaf_choices[i] if parameter.get_choice(choice).value == test[i]]
92
+ if len(choices) == 0:
93
+ result = False
94
+ messages.append(f"Value '{test[i]}' not allowed for parameter '{full_name}'")
95
+ if not result:
96
+ # print_exit_messages(messages, result)
97
+ return messages, result
98
+
99
+ #in theory it is possible that many choices have the same value,
100
+ #so we need to check each potential combination of choices
101
+ test_cases = [list(test_case) for test_case in product(*leaf_choices)]
102
+ #remove test cases that do not satisfy the constraints
103
+ constrained_test_cases = [test_case for test_case in test_cases if solver.test(test_case)]
104
+ if len(constrained_test_cases) == 0:
105
+ result = False
106
+ messages.append(f'Test case does not satisfy the constraints')
107
+ # print_exit_messages(messages, result)
108
+ return messages, result
109
+
110
+ #remove test cases that do not satisfy the assignments
111
+ def check_output_values(test_case):
112
+ output_values = [test_case[i] if not isinstance(parameters[i], OutputParameter) else parameter.default_value for i in range(len(test_case))]
113
+ output_values = solver.adapt(output_values)
114
+ test_case_copy = [test_case[i] if not isinstance(parameters[i], OutputParameter) else test[i] for i in range(len(test_case))]
115
+ the_same = True
116
+ return (test_case_copy == output_values)
117
+
118
+ assigned_test_cases = [test_case for test_case in constrained_test_cases if check_output_values(test_case)]
119
+ if len(assigned_test_cases) == 0:
120
+ messages.append(f'Output values not correct ')
121
+ #in most cases, the values of choices are unique so the length of
122
+ #constraints_test_cases should be 1. In this case, we can compare the
123
+ #result of assignment and indicate the exact differences
124
+ if len(constrained_test_cases) == 1:
125
+ test_case = copy.deepcopy(constrained_test_cases[0])
126
+ test_case = solver.adapt(test_case)
127
+ for i in range(len(test_case)):
128
+ if test_case[i] != constrained_test_cases[0][i]:
129
+ messages.append(f"Value of parameter {parameter_names[i]} should be {test_case[i]}")
130
+ # print_exit_messages(messages, False)
131
+ return messages, False
132
+
133
+ # print_exit_messages(messages, result)
134
+
135
+ return messages, result
136
+
137
+ def validate_test_stream(in_stream, function, solver, **kwargs):
138
+ """
139
+ Validates a stream of test cases. The tests that are valid are written to stdout, the invalid ones are written to stderr with error message.
140
+ The function returns True if all tests are parsed without problems. If there is a parsing error, the function returns False.
141
+ """
142
+
143
+ def print_error_messages(row, messages):
144
+ print(f'{RED}', file=sys.stderr, end='')
145
+ error_writer.writerow(row)
146
+ if not no_error_messages:
147
+ [print(f'{msg}', file=sys.stderr) for msg in messages]
148
+ print(f'{END}', file=sys.stderr, end='', flush=True)
149
+
150
+
151
+ no_error_formatting = False
152
+ if 'no_error_formatting' in kwargs:
153
+ no_error_formatting = kwargs['no_error_formatting']
154
+
155
+ GREEN = '\033[92m' if not no_error_formatting else ''
156
+ RED = '\033[91m' if not no_error_formatting else ''
157
+ END = '\033[0m' if not no_error_formatting else ''
158
+ VALID = f'{GREEN}✓{END}' if not no_error_formatting else ''
159
+ INVALID = f'{RED}✗{END}' if not no_error_formatting else ''
160
+
161
+ separator = ','
162
+ if 'separator' in kwargs:
163
+ separator = kwargs['separator']
164
+ no_headrow = False
165
+ if 'no_headrow' in kwargs:
166
+ no_headrow = kwargs['no_headrow']
167
+
168
+ use_choice_names = False
169
+ if 'use_choice_names' in kwargs:
170
+ use_choice_names = kwargs['use_choice_names']
171
+
172
+ exit_on_error = False
173
+ if 'exit_on_error' in kwargs:
174
+ exit_on_error = kwargs['exit_on_error']
175
+
176
+ no_error_messages = False
177
+ if 'no_error_messages' in kwargs:
178
+ no_error_messages = kwargs['no_error_messages']
179
+
180
+ duplicate_headrow = False
181
+ if 'duplicate_headrow' in kwargs:
182
+ duplicate_headrow = kwargs['duplicate_headrow']
183
+
184
+ #skip empty lines
185
+ in_stream = filter(lambda x: x.strip(), in_stream)
186
+ #check if there is any data to read
187
+ try:
188
+ first_line = next(in_stream)
189
+ except StopIteration:
190
+ print(f"Empty test stream", file=sys.stderr)
191
+ return False
192
+
193
+ #peek the first line to read it again
194
+ reader = csv.reader(itertools.chain([first_line], in_stream), delimiter=separator)
195
+ writer = csv.writer(sys.stdout, delimiter=separator)
196
+ error_writer = csv.writer(sys.stderr, delimiter=separator)
197
+ parsing_ok = True
198
+ if not no_headrow:
199
+ try:
200
+ headrow = next(reader)
201
+ # print(f'Validating headrow', file=sys.stderr)
202
+ messages, result = validate_headrow(headrow, function)
203
+ except csv.Error as e:
204
+ messages, result = [f"CSV parsing error: {e}"], False
205
+ parsing_ok = False
206
+
207
+ if result:
208
+ writer.writerow(headrow)
209
+ if duplicate_headrow:
210
+ error_writer.writerow(headrow)
211
+ else:
212
+ print_error_messages(headrow, messages)
213
+ if duplicate_headrow:
214
+ writer.writerow(headrow)
215
+ if exit_on_error:
216
+ return parsing_ok
217
+
218
+ while True:
219
+ try:
220
+ row = next(reader)
221
+ if not use_choice_names:
222
+ messages, result = validate_test_by_values(row, function, solver)
223
+ else:
224
+ messages, result = validate_test_by_choice_names(row, function, solver)
225
+ if result:
226
+ writer.writerow(row)
227
+ else:
228
+ print_error_messages(row, messages)
229
+ if exit_on_error:
230
+ return parsing_ok
231
+
232
+ except StopIteration:
233
+ break
234
+ except csv.Error as e:
235
+ print(f"CSV parsing error while fetching next test: {e}", file=sys.stderr)
236
+ parsing_ok = False
237
+ continue
238
+ return parsing_ok