testsuite.py

changeset 65
f2dc17b830e0
parent 64
1c0884f5506e
equal deleted inserted replaced
64:1c0884f5506e 65:f2dc17b830e0
1 #!/usr/bin/env python3
2 '''
3 Routines for assembling the test suite, and running it on a model.
4 '''
1 from warnings import warn 5 from warnings import warn
2 6
3 class ProblemType: 7 class ProblemType:
8 '''
9 Represents a type of issue in the part
10 '''
4 severities = ['hold', 'warning'] # in descending order 11 severities = ['hold', 'warning'] # in descending order
5 def __init__(self, name, severity, message): 12 def __init__(self, name, severity, message):
6 if severity not in ProblemType.severities: 13 if severity not in ProblemType.severities:
7 raise ValueError(str.format( 14 raise ValueError(str.format(
8 'bad severity {severity!r}', 15 'bad severity {severity!r}',
16 problem_class = self, 23 problem_class = self,
17 bad_object = bad_object, 24 bad_object = bad_object,
18 **args, 25 **args,
19 ) 26 )
20 def placeholder_message(self): 27 def placeholder_message(self):
28 '''
29 Returns the error message of this problem type, with placeholders
30 filled in.
31 '''
21 if callable(self.message): 32 if callable(self.message):
22 import inspect 33 import inspect
23 spec = inspect.getfullargspec(self.message) 34 spec = inspect.getfullargspec(self.message)
24 args = {} 35 args = {}
25 assert not spec.varargs and not spec.varkw 36 assert not spec.varargs and not spec.varkw
28 return self.message(**args) 39 return self.message(**args)
29 else: 40 else:
30 return self.message 41 return self.message
31 42
32 class Problem: 43 class Problem:
44 '''
45 Represents a single issue in the part
46 '''
33 def __init__(self, problem_class, bad_object, **args): 47 def __init__(self, problem_class, bad_object, **args):
34 self.problem_class = problem_class 48 self.problem_class = problem_class
35 self.severity = problem_class.severity 49 self.severity = problem_class.severity
36 self.object = bad_object 50 self.object = bad_object
37 self.args = args 51 self.args = args
40 return self.problem_class.message(**self.args) 54 return self.problem_class.message(**self.args)
41 else: 55 else:
42 return self.problem_class.message 56 return self.problem_class.message
43 57
44 def problem_type(problem_name, **args): 58 def problem_type(problem_name, **args):
59 '''
60 A decorator that creates a new ProblemType and attaches it to the
61 decoratable function.
62 '''
45 def wrapper(function): 63 def wrapper(function):
46 if not hasattr(function, 'ldcheck_problem_types'): 64 if not hasattr(function, 'ldcheck_problem_types'):
47 function.ldcheck_problem_types = {} 65 function.ldcheck_problem_types = {}
48 new_type = ProblemType(name = problem_name, **args) 66 new_type = ProblemType(name = problem_name, **args)
49 function.ldcheck_problem_types[problem_name] = new_type 67 function.ldcheck_problem_types[problem_name] = new_type
50 return function 68 return function
51 return wrapper 69 return wrapper
52 70
53 def report_problem(problem_name, *, bad_object, **args): 71 def report_problem(problem_name, *, bad_object, **args):
72 '''
73 Called from test functions to report issues. This gets later mapped
74 into Problem objects.
75 '''
54 return {'type': problem_name, 'bad-object': bad_object, 'args': args} 76 return {'type': problem_name, 'bad-object': bad_object, 'args': args}
55 77
56 def name_of_package(package): 78 def name_of_package(package):
57 if isinstance(package, tuple): 79 if isinstance(package, tuple):
58 return package[1] 80 return package[1]
85 warn(str.format('Module {} does not have a manifest', module_name)) 107 warn(str.format('Module {} does not have a manifest', module_name))
86 test_suite['tests'].sort(key = lambda f: f.__name__) 108 test_suite['tests'].sort(key = lambda f: f.__name__)
87 return test_suite 109 return test_suite
88 110
89 def problem_key(problem): 111 def problem_key(problem):
112 '''
113 Sorts problems by rank and line number.
114 '''
90 rank = ProblemType.severities.index(problem.severity) # sort by severity 115 rank = ProblemType.severities.index(problem.severity) # sort by severity
91 return (rank, problem.line_number) 116 return (rank, problem.line_number)
92 117
93 def build_problem(test_function, problem_params): 118 def build_problem(test_function, problem_params):
94 problem_name = problem_params['type'] 119 problem_name = problem_params['type']
98 **problem_params['args'], 123 **problem_params['args'],
99 ) 124 )
100 return problem_object 125 return problem_object
101 126
102 def check_model(model, test_suite = None): 127 def check_model(model, test_suite = None):
128 '''
129 Runs the test suite on the model and prepares a report.
130 '''
103 if not test_suite: 131 if not test_suite:
104 test_suite = load_tests() 132 test_suite = load_tests()
105 problems = [] 133 problems = []
106 line_numbers = { 134 line_numbers = {
107 element: (i, i + 1) 135 element: (i, i + 1)
121 for problem in problems 149 for problem in problems
122 ), 150 ),
123 'problems': sorted(problems, key = problem_key), 151 'problems': sorted(problems, key = problem_key),
124 } 152 }
125 153
126 def problem_text(problem, test_suite):
127 message = problem.problem_class.message
128 if callable(message):
129 message = message(**problem.args)
130 return message
131
132 def format_report_html(report, model, test_suite):
133 messages = []
134 for problem in report['problems']:
135 ldraw_code = model.body[problem.body_index].textual_representation()
136 message = str.format(
137 '<li class="{problem_type}">{model_name}:{line_number}:'
138 '{problem_type}: {message}<br />{ldraw_code}</li>',
139 model_name = model.name,
140 line_number = problem.line_number,
141 problem_type = problem.severity,
142 message = problem_text(problem, test_suite),
143 ldraw_code = ldraw_code,
144 )
145 messages.append(message)
146 return '\n'.join(messages)
147
148 def format_report(report, model, test_suite):
149 import colorama
150 colorama.init()
151 messages = []
152 for problem in report['problems']:
153 if problem.severity == 'hold':
154 text_colour = colorama.Fore.LIGHTRED_EX
155 elif problem.severity == 'warning':
156 text_colour = colorama.Fore.LIGHTBLUE_EX
157 else:
158 text_colour = ''
159 ldraw_code = model.body[problem.body_index].textual_representation()
160 message = str.format(
161 '{text_colour}{model_name}:{line_number}: {problem_type}: {message}'
162 '{colour_reset}\n\t{ldraw_code}',
163 text_colour = text_colour,
164 model_name = model.name,
165 line_number = problem.line_number,
166 problem_type = problem.severity,
167 message = problem_text(problem, test_suite),
168 colour_reset = colorama.Fore.RESET,
169 ldraw_code = ldraw_code,
170 )
171 messages.append(message)
172 return '\n'.join(messages)
173
174 def iterate_problems(test_suite): 154 def iterate_problems(test_suite):
155 '''
156 Yields all problem types.
157 '''
175 for test_function in test_suite['tests']: 158 for test_function in test_suite['tests']:
176 yield from test_function.ldcheck_problem_types.values() 159 yield from test_function.ldcheck_problem_types.values()
177 160
178 def all_problem_types(test_suite): 161 def all_problem_types(test_suite):
162 '''
163 Returns a sorted list of problem types.
164 '''
179 return sorted( 165 return sorted(
180 iterate_problems(test_suite), 166 iterate_problems(test_suite),
181 key = lambda problem_type: problem_type.name 167 key = lambda problem_type: problem_type.name
182 ) 168 )
183
184
185 if __name__ == '__main__':
186 from pprint import pprint
187 pprint(load_tests())

mercurial