|
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}', |
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'] |
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()) |
|